Skip to content

Commit

Permalink
Merge pull request #267 from kristerkari/bugfix/at-function-named-arg…
Browse files Browse the repository at this point in the history
…uments-css-function-parameters

Fix using data urls in function args with a better args parsing
  • Loading branch information
kristerkari authored Aug 12, 2018
2 parents f9fdc87 + 9e4b6c5 commit ca633b3
Show file tree
Hide file tree
Showing 6 changed files with 357 additions and 34 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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";
Loading

0 comments on commit ca633b3

Please sign in to comment.