Repository: stylelint/stylelint

Issue:
Fix `*-case|*-unknown` false positives for CSS-in-JS template literals
### What steps are needed to reproduce the bug?

It seems that rule `function-no-unknown` is triggering a false positive when functions are called within an anonymous function

```
import styled from "styled-components"

function getColor() {
  return "red"
}

const Foo = styled.div`
  color: ${() => getColor()}; // Unexpected unknown function "getColor"
`

const Bar = styled.div`
  color: ${getColor()}; // OK
`
```




### What Stylelint configuration is needed to reproduce the bug?

```
module.exports = {
  extends: [
    "stylelint-config-standard",
  ],
  rules: {},
  customSyntax: "postcss-styled-syntax",
}
```

### How did you run Stylelint?

`stylelint \"./src/**/*.tsx\"`

### Which version of Stylelint are you using?

`15.1.0`

```
"postcss-styled-syntax": "0.3.1",
"stylelint": "15.1.0",
"stylelint-config-standard": "30.0.1"
```

### What did you expect to happen?

It should not report `Unexpected unknown function "getColor"` since we are inside a literal expression

### What actually happened?

```
✖  Unexpected unknown function "getColor"  function-no-unknown
```

### Does the bug relate to non-standard syntax?

yes, css-in-js

### Proposal to fix the bug

_No response_

Hints from issue comments:
@jeddy3: @Nume Thanks for the report and for using the template.

Please raise an issue in the [`postcss-styled-syntax` tracker](https://github.com/hudochenkov/postcss-styled-syntax/issues) as custom syntaxes are responsible for extracting CSS out of the container and passing it along to Stylelint's built-in rules.

Closing here as related to a community custom syntax rather than Stylelint itself.@Nume: It was previously opened at postcss-styled-syntax https://github.com/hudochenkov/postcss-styled-syntax/issues/6 but closed as related to bug in rule and not the custom syntax :)@jeddy3: @Nume Thanks for the update.

As requested in https://github.com/hudochenkov/postcss-styled-syntax/issues/6#issuecomment-1428343647, I'll tag @hudochenkov to take a look.@eMerzh: just to add my 2cents 
i don't think it's specially linked to this rule... 

I got the same issue with unkown function but also others like 

```css
/*  17:39  ✖  Expected "hexToRgb" to be "hextorgb"  function-name-case */
box-shadow: 0 0 4px rgba(${props => hexToRgb(prop('colors.primary')(props))}, 0.5);

/* ALL GOOD*/
box-shadow: ${hexToRgb('props')};
```

i would vote for the way the ()=> functions are parsed or something@hudochenkov: Identified more rules in my work project, which shows similar symptom: trying to lint non-standard CSS declaration value.

- `unit-no-unknown`
- `value-keyword-case`
- `annotation-no-unknown`

---

```js
const ListStyled = styled.div`
    padding: ${(props) => (props.isContained ? `0 ${token.spacing5}` : `${token.spacing3} 0`)};
`;
```

```
src/components/List/List.tsx
 2:89  ✖  Unexpected unknown unit "`"  unit-no-unknown
```

---

```js
const BadgeStyled = styled.div`
    height: ${(props) => (props.isSmall ? `20px` : `24px`)};
`;
```

```
src/components/Badge/Badge.tsx
   2:24  ⚠  Expected "props.isSmall" to be "props.issmall"            value-keyword-case
```

---

```js
const Container = styled.label`
    cursor: ${(props) => !props.isDisabled && 'pointer'};
`;
```

```
src/components/Checkbox/Checkbox.tsx
 2:23  ✖  Unexpected unknown annotation "!props.isDisabled"  annotation-no-unknown
```
@alexkuz: @hudochenkov there is the same kind of issue with `media-feature-name-no-unknown`:

```
  @media (${(p) => p.theme.breakpoints.netbook}) {
    position: absolute;
```
```
42:14  ✖  Unexpected unknown media feature name "p"  media-feature-name-no-unknown
```@jeddy3: @alexkuz Please create [a new issue](https://github.com/stylelint/stylelint/issues/new?assignees=&labels=&template=REPORT_A_BUG.yml) so that we can track the bug.@Mouvedia: > there is the same kind of issue with `media-feature-name-no-unknown`:
> 
> ```
>   @media (${(p) => p.theme.breakpoints.netbook}) {
>     position: absolute;
> ```
> 
> ```
> 42:14  ✖  Unexpected unknown media feature name "p"  media-feature-name-no-unknown
> ```

This was fixed by version [15.6.2](https://github.com/stylelint/stylelint/releases/tag/15.6.2).

Solution patch:
diff --git a/.changeset/five-waves-relax.md b/.changeset/five-waves-relax.md
new file mode 100644
index 000000000..7c8f21bb1
--- /dev/null
+++ b/.changeset/five-waves-relax.md
@@ -0,0 +1,9 @@
+---
+"stylelint": patch
+---
+
+Fixed: `annotation-no-unknown` false positives for CSS-in-JS template literals
+Fixed: `function-name-case` false positives for CSS-in-JS template literals
+Fixed: `function-no-unknown` false positives for CSS-in-JS template literals
+Fixed: `unit-no-unknown` false positives for CSS-in-JS template literals
+Fixed: `value-keyword-case` false positives for CSS-in-JS template literals
diff --git a/docs/migration-guide/to-15.md b/docs/migration-guide/to-15.md
index 5d072ab28..b8e62e4b4 100644
--- a/docs/migration-guide/to-15.md
+++ b/docs/migration-guide/to-15.md
@@ -47,7 +47,17 @@ We've removed the [deprecated rules](../user-guide/rules.md#deprecated) from the
 
 There are lots of other rules we don't turn on in the [standard config](https://www.npmjs.com/package/stylelint-config-standard) and you can learn more about using them to customize Stylelint to your exact needs in our [new guide](../user-guide/customize.md).
 
-If you want to continue using Stylelint to enforce stylistic consistency, you can [migrate the deprecated rules you need to a plugin](../developer-guide/plugins.md).
+Alternatively, you can [migrate the deprecated rules you need to a plugin](../developer-guide/plugins.md) if you want to continue using Stylelint to enforce stylistic consistency.
+
+To migrate a rule to a plugin, you can:
+
+1. Copy the rule, test, util and README file you need
+2. Correct import paths
+3. Prefix the:
+   - relevant functions with [`stylelint.utils`](../developer-guide/plugins.md#stylelintutils)
+   - rule name, e.g. `stylistic/indentation`
+
+Please let us know once you've published the plugin to NPM so that we can add a link to it in this migration guide.
 
 ### Added `declaration-property-value-no-unknown` rule
 
diff --git a/lib/rules/annotation-no-unknown/index.js b/lib/rules/annotation-no-unknown/index.js
index 6759ec13e..a1a187f19 100644
--- a/lib/rules/annotation-no-unknown/index.js
+++ b/lib/rules/annotation-no-unknown/index.js
@@ -3,6 +3,7 @@
 const valueParser = require('postcss-value-parser');
 
 const getDeclarationValue = require('../../utils/getDeclarationValue');
+const isStandardSyntaxValue = require('../../utils/isStandardSyntaxValue');
 const optionsMatches = require('../../utils/optionsMatches');
 const report = require('../../utils/report');
 const ruleMessages = require('../../utils/ruleMessages');
@@ -45,6 +46,8 @@ const rule = (primary, secondaryOptions) => {
 		 * @param {import('postcss').Declaration} decl
 		 */
 		function checkStatement(decl) {
+			if (!isStandardSyntaxValue(decl.value)) return;
+
 			if (decl.important) return;
 
 			if (!decl.value.includes('!')) return;
diff --git a/lib/rules/function-name-case/index.js b/lib/rules/function-name-case/index.js
index f6d129c4c..40a8db5cc 100644
--- a/lib/rules/function-name-case/index.js
+++ b/lib/rules/function-name-case/index.js
@@ -11,6 +11,7 @@ const setDeclarationValue = require('../../utils/setDeclarationValue');
 const validateOptions = require('../../utils/validateOptions');
 const valueParser = require('postcss-value-parser');
 const { isRegExp, isString } = require('../../utils/validateTypes');
+const isStandardSyntaxValue = require('../../utils/isStandardSyntaxValue');
 
 const ruleName = 'function-name-case';
 
@@ -53,6 +54,8 @@ const rule = (primary, secondaryOptions, context) => {
 		}
 
 		root.walkDecls((decl) => {
+			if (!isStandardSyntaxValue(decl.value)) return;
+
 			let needFix = false;
 			const parsed = valueParser(getDeclarationValue(decl));
 
diff --git a/lib/rules/function-no-unknown/index.js b/lib/rules/function-no-unknown/index.js
index f996f71fe..0c89682b0 100644
--- a/lib/rules/function-no-unknown/index.js
+++ b/lib/rules/function-no-unknown/index.js
@@ -12,6 +12,7 @@ const validateOptions = require('../../utils/validateOptions');
 const isStandardSyntaxFunction = require('../../utils/isStandardSyntaxFunction');
 const isCustomFunction = require('../../utils/isCustomFunction');
 const { isRegExp, isString } = require('../../utils/validateTypes');
+const isStandardSyntaxValue = require('../../utils/isStandardSyntaxValue');
 
 const ruleName = 'function-no-unknown';
 
@@ -57,6 +58,8 @@ const rule = (primary, secondaryOptions) => {
 		root.walkDecls((decl) => {
 			const { value } = decl;
 
+			if (!isStandardSyntaxValue(value)) return;
+
 			valueParser(value).walk((node) => {
 				const name = node.value;
 
diff --git a/lib/rules/unit-no-unknown/index.js b/lib/rules/unit-no-unknown/index.js
index fa53587b4..09cf46214 100644
--- a/lib/rules/unit-no-unknown/index.js
+++ b/lib/rules/unit-no-unknown/index.js
@@ -15,6 +15,7 @@ const valueParser = require('postcss-value-parser');
 const vendor = require('../../utils/vendor');
 const { isRegExp, isString, assert } = require('../../utils/validateTypes');
 const { isAtRule } = require('../../utils/typeGuards');
+const isStandardSyntaxValue = require('../../utils/isStandardSyntaxValue');
 
 const ruleName = 'unit-no-unknown';
 
@@ -158,9 +159,9 @@ const rule = (primary, secondaryOptions) => {
 			check(atRule, atRule.params, atRuleParamIndex);
 		});
 		root.walkDecls((decl) => {
-			if (!isStandardSyntaxDeclaration(decl)) {
-				return;
-			}
+			if (!isStandardSyntaxDeclaration(decl)) return;
+
+			if (!isStandardSyntaxValue(decl.value)) return;
 
 			check(decl, decl.value, declarationValueIndex);
 		});
diff --git a/lib/rules/value-keyword-case/index.js b/lib/rules/value-keyword-case/index.js
index 818b8cf6f..43e11633f 100644
--- a/lib/rules/value-keyword-case/index.js
+++ b/lib/rules/value-keyword-case/index.js
@@ -79,6 +79,8 @@ const rule = (primary, secondaryOptions, context) => {
 			const propLowerCase = decl.prop.toLowerCase();
 			const value = decl.value;
 
+			if (!isStandardSyntaxValue(value)) return;
+
 			const parsed = valueParser(getDeclarationValue(decl));
 
 			let needFix = false;



Test patch:
diff --git a/lib/rules/annotation-no-unknown/__tests__/index.js b/lib/rules/annotation-no-unknown/__tests__/index.js
index c508f01e0..3ec8513f0 100644
--- a/lib/rules/annotation-no-unknown/__tests__/index.js
+++ b/lib/rules/annotation-no-unknown/__tests__/index.js
@@ -56,3 +56,15 @@ testRule({
 		},
 	],
 });
+
+testRule({
+	ruleName,
+	customSyntax: 'postcss-scss',
+	config: [true],
+
+	accept: [
+		{
+			code: 'a { color: #{$green} !imprtant; }',
+		},
+	],
+});
diff --git a/lib/rules/function-name-case/__tests__/index.js b/lib/rules/function-name-case/__tests__/index.js
index 7603a5186..5a7e02aff 100644
--- a/lib/rules/function-name-case/__tests__/index.js
+++ b/lib/rules/function-name-case/__tests__/index.js
@@ -583,3 +583,15 @@ testRule({
 		},
 	],
 });
+
+testRule({
+	ruleName,
+	customSyntax: 'postcss-scss',
+	config: ['upper'],
+	fix: true,
+	accept: [
+		{
+			code: 'a { margin: cAlC(5% - #{$size2}); }',
+		},
+	],
+});
diff --git a/lib/rules/unit-no-unknown/__tests__/index.js b/lib/rules/unit-no-unknown/__tests__/index.js
index 658a8afbd..c78cceeb7 100644
--- a/lib/rules/unit-no-unknown/__tests__/index.js
+++ b/lib/rules/unit-no-unknown/__tests__/index.js
@@ -490,8 +490,12 @@ testRule({
 			description: 'ignore interpolation',
 		},
 		{
-			code: 'a { margin: calc(100% - #{$margin * 2}); }',
-			description: 'work with interpolation',
+			code: 'a { margin: calc(100% - #{$margin * 2pix}); }',
+			description: 'ignore interpolations',
+		},
+		{
+			code: 'a { margin: $margin + 10pix; }',
+			description: 'ignore values with variables',
 		},
 	],
 
@@ -504,22 +508,6 @@ testRule({
 			endLine: 1,
 			endColumn: 18,
 		},
-		{
-			code: 'a { margin: $margin + 10pix; }',
-			message: messages.rejected('pix'),
-			line: 1,
-			column: 25,
-			endLine: 1,
-			endColumn: 28,
-		},
-		{
-			code: 'a { margin: calc(100% - #{$margin * 2pix}); }',
-			message: messages.rejected('pix'),
-			line: 1,
-			column: 38,
-			endLine: 1,
-			endColumn: 42,
-		},
 	],
 });