Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix using data urls in function args with a better args parsing #267

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
@@ -1,3 +1,7 @@
# HEAD

- Fixed: `at-function-named-arguments` correctly parse data uris as function parameters.

# 3.2.0

- Added: `no-dollar-variables` rule.
Expand Down
41 changes: 41 additions & 0 deletions src/rules/at-function-named-arguments/__tests__/index.js
Expand Up @@ -32,6 +32,15 @@ testRule(rule, {
`,
description: "Always. Example: native CSS function is ignored."
},
{
code: `
.c {
background-image: test($value: url("data:image/svg+xml;charset=utf8,%3C"));
}
`,
description:
"Always. Example: native CSS function is ignored inside a function call."
},
{
code: `
.b {
Expand Down Expand Up @@ -231,6 +240,17 @@ testRule(rule, {
column: 9,
message: messages.expected,
description: "Always. Example: mixed named arguments."
},
{
code: `
.c {
background-image: test(url("data:image/svg+xml;charset=utf8,%3C"));
}
`,
line: 3,
column: 9,
description:
"Always. Example: native CSS function inside a function call."
}
]
});
Expand Down Expand Up @@ -339,6 +359,15 @@ testRule(rule, {
`,
description:
"Never. Example: single argument is an interpolated value and not named."
},
{
code: `
.c {
background-image: test(url("data:image/svg+xml;charset=utf8,%3C"));
}
`,
description:
"Always. Example: native CSS function is ignored inside a function call."
}
],

Expand Down Expand Up @@ -458,6 +487,18 @@ testRule(rule, {
column: 9,
message: messages.rejected,
description: "Never. Example: mixed named arguments."
},
{
code: `
.c {
background-image: test($value: url("data:image/svg+xml;charset=utf8,%3C"));
}
`,
line: 3,
column: 9,
message: messages.rejected,
description:
"Always. Example: native CSS function inside a function call."
}
]
});
Expand Down
37 changes: 3 additions & 34 deletions src/rules/at-function-named-arguments/index.js
Expand Up @@ -2,7 +2,8 @@ import { utils } from "stylelint";
import {
namespace,
optionsHaveIgnored,
isNativeCssFunction
isNativeCssFunction,
parseFunctionArguments
} from "../../utils";
import valueParser from "postcss-value-parser";

Expand All @@ -13,7 +14,6 @@ export const messages = utils.ruleMessages(ruleName, {
rejected: "Unexpected a named parameter in function call"
});

const hasArgumentsRegExp = /\((.*)\)$/;
const isScssVarRegExp = /^\$\S*/;

export default function(expectation, options) {
Expand Down Expand Up @@ -52,38 +52,7 @@ export default function(expectation, options) {
return;
}

const argsString = decl.value
.replace(/\n/g, " ")
.match(hasArgumentsRegExp);

// Ignore @include that does not contain arguments.
if (
!argsString ||
argsString.index === -1 ||
argsString[0].length === 2
) {
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 args = parseFunctionArguments(decl.value);
const isSingleArgument = args.length === 1;

if (isSingleArgument && shouldIgnoreSingleArgument) {
Expand Down
251 changes: 251 additions & 0 deletions src/utils/__tests__/parseFunctionArguments.js
@@ -0,0 +1,251 @@
import {
groupByKeyValue,
mapToKeyValue,
parseFunctionArguments
} from "../parseFunctionArguments";

describe("groupByKeyValue", () => {
it("should group key with values", () => {
expect(
groupByKeyValue([
{ type: "word", sourceIndex: 6, value: "$value" },
{
type: "div",
sourceIndex: 12,
value: ":",
before: "",
after: " "
},
{ type: "word", sourceIndex: 14, value: "40px" },
{
type: "div",
sourceIndex: 18,
value: ",",
before: "",
after: " "
},
{ type: "word", sourceIndex: 20, value: "10px" }
])
).toEqual([
[
{ sourceIndex: 6, type: "word", value: "$value" },
{ after: " ", before: "", sourceIndex: 12, type: "div", value: ":" },
{ sourceIndex: 14, type: "word", value: "40px" }
],
[{ sourceIndex: 20, type: "word", value: "10px" }]
]);
expect(
groupByKeyValue([
{ type: "word", sourceIndex: 6, value: "$value" },
{
type: "div",
sourceIndex: 12,
value: ":",
before: "",
after: " "
},
{ type: "word", sourceIndex: 14, value: "40px" },
{
type: "div",
sourceIndex: 18,
value: ",",
before: "",
after: " "
},
{ type: "word", sourceIndex: 20, value: "$second-value" },
{
type: "div",
sourceIndex: 33,
value: ":",
before: "",
after: " "
},
{ type: "word", sourceIndex: 35, value: "10px" },
{
type: "div",
sourceIndex: 39,
value: ",",
before: "",
after: " "
},
{ type: "word", sourceIndex: 41, value: "$color" },
{
type: "div",
sourceIndex: 47,
value: ":",
before: "",
after: " "
},
{ type: "string", sourceIndex: 49, quote: "'", value: "black" }
])
).toEqual([
[
{ sourceIndex: 6, type: "word", value: "$value" },
{ after: " ", before: "", sourceIndex: 12, type: "div", value: ":" },
{ sourceIndex: 14, type: "word", value: "40px" }
],
[
{ sourceIndex: 20, type: "word", value: "$second-value" },
{ after: " ", before: "", sourceIndex: 33, type: "div", value: ":" },
{ sourceIndex: 35, type: "word", value: "10px" }
],
[
{ sourceIndex: 41, type: "word", value: "$color" },
{ after: " ", before: "", sourceIndex: 47, type: "div", value: ":" },
{ quote: "'", sourceIndex: 49, type: "string", value: "black" }
]
]);
});
});

describe("mapToKeyValue", () => {
expect(
mapToKeyValue([
{ sourceIndex: 6, type: "word", value: "$value" },
{ after: " ", before: "", sourceIndex: 12, type: "div", value: ":" },
{ sourceIndex: 14, type: "word", value: "40px" }
])
).toEqual({ key: "$value", value: "40px" });
expect(
mapToKeyValue([{ sourceIndex: 20, type: "word", value: "10px" }])
).toEqual({ value: "10px" });
});

describe("parseFunctionArguments", () => {
it("ignores empty string", () => {
expect(parseFunctionArguments("")).toEqual([]);
});

it("ignores value outside a function", () => {
expect(parseFunctionArguments("1")).toEqual([]);
});

it("parses function call", () => {
expect(parseFunctionArguments("func()")).toEqual([]);
});

it("parses number as the value", () => {
expect(parseFunctionArguments("func(1)")).toEqual([
{
value: "1"
}
]);
});

it("parses calculation as the value", () => {
expect(parseFunctionArguments("func(30 * 25ms)")).toEqual([
{
value: "30 * 25ms"
}
]);
});

it("parses multiple args", () => {
expect(parseFunctionArguments("func(1, 2)")).toEqual([
{
value: "1"
},
{
value: "2"
}
]);
});

it("parses variable as the key and a number as the value", () => {
expect(parseFunctionArguments("func($var: 1)")).toEqual([
{
key: "$var",
value: "1"
}
]);
});

it("parses variable as the key and a CSS function as the value", () => {
expect(
parseFunctionArguments(
'test($foo: url("data:image/svg+xml;charset=utf8,%3C"))'
)
).toEqual([
{
key: "$foo",
value: 'url("data:image/svg+xml;charset=utf8,%3C")'
}
]);
});

it("parses variable as the key and interpolation as the value", () => {
expect(parseFunctionArguments("reset($value: #{$other-value})")).toEqual([
{
key: "$value",
value: "#{$other-value}"
}
]);
});

it("parses variable as the key and a calculated value", () => {
expect(parseFunctionArguments("anim($duration: 30 * 25ms)")).toEqual([
{
key: "$duration",
value: "30 * 25ms"
}
]);
});

it("parses 2 key value parameters", () => {
expect(parseFunctionArguments("func($var: 1, $foo: bar)")).toEqual([
{
key: "$var",
value: "1"
},
{
key: `$foo`,
value: "bar"
}
]);
});

it("parses 2 key value parameters", () => {
expect(
parseFunctionArguments(
"reset($value: 40px, $second-value: 10px, $color: 'black')"
)
).toEqual([
{
key: "$value",
value: "40px"
},
{
key: "$second-value",
value: "10px"
},
{
key: "$color",
value: "'black'"
}
]);
});

it("parses linear-gradient", () => {
expect(
parseFunctionArguments(
"linear-gradient(to left, #333, #333 50%, #eee 75%, #333 75%);"
)
).toEqual([
{
value: "to left"
},
{
value: "#333"
},
{
value: "#333 50%"
},
{
value: "#eee 75%"
},
{
value: "#333 75%"
}
]);
});
});
1 change: 1 addition & 0 deletions src/utils/index.js
Expand Up @@ -27,3 +27,4 @@ export { default as parseSelector } from "./parseSelector";
export { default as findOperators } from "./sassValueParser";
export { default as rawNodeString } from "./rawNodeString";
export { default as whitespaceChecker } from "./whitespaceChecker";
export { parseFunctionArguments } from "./parseFunctionArguments";