Skip to content
This repository has been archived by the owner on Feb 18, 2022. It is now read-only.

Commit

Permalink
Not throw error for circular variable references
Browse files Browse the repository at this point in the history
  • Loading branch information
Glen Huang committed Apr 4, 2015
1 parent d0082b2 commit e998928
Show file tree
Hide file tree
Showing 10 changed files with 98 additions and 46 deletions.
97 changes: 58 additions & 39 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,11 @@ var RE_VAR = /([\w-]+)(?:\s*,\s*)?(.*)?/ // matches `name[, fallback]`, captures
*
* @param {String} value A property value known to contain CSS variable functions
* @param {Object} variables A map of variable names and values
* @param {Array} deps An array of variable names the current variable depends on
* @param {Object} source source object of the declaration containing the rule
* @return {String} A property value with all CSS variables substituted.
*/

function resolveValue(value, variables, deps, source) {
if (!deps) {
deps = []
}

function resolveValue(value, variables, source) {
var results = []

var start = value.indexOf(VAR_FUNC_IDENTIFIER + "(")
Expand All @@ -52,52 +47,66 @@ function resolveValue(value, variables, deps, source) {
}

matches.body.replace(RE_VAR, function(_, name, fallback) {
if (deps.indexOf(name) !== -1) {
throw new Error("circular variable reference: " + name)
}
var replacement = variables[name]
if (!replacement && !fallback) {
var variable = variables[name]
var post
// undefined and without fallback, just keep original value
if (!variable && !fallback) {
console.warn(helpers.message("variable '" + name + "' is undefined and used without a fallback", source))
post = matches.post ? resolveValue(matches.post, variables, source) : [""]
// resolve the end of the expression
post.forEach(function(afterValue) {
results.push(value.slice(0, start) + VAR_FUNC_IDENTIFIER + "(" + name + ")" + afterValue)
})
return
}
var resolved, post

// prepend with fallbacks
if (fallback) {
// resolve fallback values
resolved = resolveValue(fallback, variables, [], source)
fallback = resolveValue(fallback, variables, source)
// resolve the end of the expression before the rest
post = matches.post ? resolveValue(matches.post, variables, [], source) : [""]
resolved.forEach(function(fbValue) {
post = matches.post ? resolveValue(matches.post, variables, source) : [""]
fallback.forEach(function(fbValue) {
post.forEach(function(afterValue) {
results.push(value.slice(0, start) + fbValue + afterValue)
})
})
}

if (!variable) {
return
}

// replace with computed custom properties
if (replacement) {
deps.push(name)
// resolve replacement if it use a custom property
if (!Array.isArray(replacement)) {
replacement = resolveValue(replacement, variables, deps, source)
variables[name] = replacement
if (!variable.resolved) {
// circular reference encountered
if (variable.deps.indexOf(name) !== -1) {
if (!fallback) {
console.warn(helpers.message("circular variable reference: " + name, source))
variable.value = [variable.value]
variable.circular = true
}
else {
variable.value = fallback
return
}
}
// resolve the end of the expression
post = matches.post ? resolveValue(matches.post, variables, [], source) : [""]
replacement.forEach(function(replacementValue) {
post.forEach(function(afterValue) {
results.push(value.slice(0, start) + replacementValue + afterValue)
})
})
else {
variable.deps.push(name)
variable.value = resolveValue(variable.value, variables, source)
}
variable.resolved = true
}

// nothing, just keep original value
if (!replacement && !fallback) {
resolved = matches.post ? resolveValue(matches.post, variables, [], source) : [""]
// resolve the end of the expression
resolved.forEach(function(afterValue) {
results.push(value.slice(0, start) + VAR_FUNC_IDENTIFIER + "(" + name + ")" + afterValue)
})
if (variable.circular && fallback) {
return
}
// resolve the end of the expression
post = matches.post ? resolveValue(matches.post, variables, source) : [""]
variable.value.forEach(function(replacementValue) {
post.forEach(function(afterValue) {
results.push(value.slice(0, start) + replacementValue + afterValue)
})
})
})

return results
Expand Down Expand Up @@ -150,7 +159,12 @@ module.exports = function(options) {
var prop = decl.prop
if (prop && prop.indexOf(VAR_PROP_IDENTIFIER) === 0) {
if (!map[prop] || !importantMap[prop] || decl.important) {
map[prop] = decl.value
map[prop] = {
value: decl.value,
deps: [],
circular: false,
resolved: false,
}
importantMap[prop] = decl.important
}
toRemove.push(index)
Expand All @@ -172,7 +186,12 @@ module.exports = function(options) {

// apply js-defined custom properties
Object.keys(variables).forEach(function(variable) {
map[variable] = variables[variable]
map[variable] = {
value: variables[variable],
deps: [],
circular: false,
resolved: false,
}
})

// resolve variables
Expand All @@ -185,7 +204,7 @@ module.exports = function(options) {
}

helpers.try(function resolve() {
resolveValue(value, map, [], decl.source).forEach(function(resolvedValue) {
resolveValue(value, map, decl.source).forEach(function(resolvedValue) {
var clone = decl.cloneBefore()
clone.value = resolvedValue
})
Expand Down
3 changes: 3 additions & 0 deletions test/fixtures/circular-reference.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@
--color: var(--bg-color);
--bg-color: var(--color);
}
body {
color: var(--color);
}
3 changes: 3 additions & 0 deletions test/fixtures/circular-reference.expected.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
body {
color: var(--bg-color);
}
6 changes: 6 additions & 0 deletions test/fixtures/self-reference-double-fallback.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
:root {
--color: var(--color, #aaa);
}
body {
color: var(--color, #bbb);
}
4 changes: 4 additions & 0 deletions test/fixtures/self-reference-double-fallback.expected.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
body {
color: #bbb;
color: #aaa;
}
6 changes: 6 additions & 0 deletions test/fixtures/self-reference-fallback.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
:root {
--color: var(--color);
}
body {
color: var(--color, #aaa);
}
3 changes: 3 additions & 0 deletions test/fixtures/self-reference-fallback.expected.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
body {
color: #aaa;
}
3 changes: 3 additions & 0 deletions test/fixtures/self-reference.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
:root {
--color: var(--color);
}
body {
color: var(--color);
}
3 changes: 3 additions & 0 deletions test/fixtures/self-reference.expected.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
body {
color: var(--color);
}
16 changes: 9 additions & 7 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,14 @@ test("preserves variables when `preserve` is `true`", function(t) {
t.end()
})

test("throw error for circular variable references", function(t) {
t.throws(function() {
resolveFixture("self-reference", {preserve: true})
}, Error, "should throw error for self-referential variables")
t.throws(function() {
resolveFixture("circular-reference", {preserve: true})
}, Error, "should throw error for circular variable references")
test("circular variable references", function(t) {
compareFixtures(t, "self-reference")
compareFixtures(t, "circular-reference")
t.end()
})

test("circular variable references with fallback", function(t) {
compareFixtures(t, "self-reference-fallback")
compareFixtures(t, "self-reference-double-fallback")
t.end()
})

0 comments on commit e998928

Please sign in to comment.