From f76bab512f8094e157cf283d8d689f57b2af74ee Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sun, 23 Dec 2012 13:19:20 -0700 Subject: [PATCH] Most recently added scoped properties win in case of a specificity tie This makes the scoped property system mimic the behavior of CSS. When there is a tie, the scoped properties loaded later in the cascade win. I also optimize the scanning of all the properties, checking only those sets of properties that have a value for the desired key path, to reduce the need to match a ton of scope selectors. --- spec/app/syntax-spec.coffee | 7 ++++++ src/app/syntax.coffee | 46 ++++++++++++++++++++++--------------- 2 files changed, 35 insertions(+), 18 deletions(-) diff --git a/spec/app/syntax-spec.coffee b/spec/app/syntax-spec.coffee index 722d6db91..c3117fb46 100644 --- a/spec/app/syntax-spec.coffee +++ b/spec/app/syntax-spec.coffee @@ -10,3 +10,10 @@ describe "the `syntax` global", -> expect(syntax.getProperty([".source.js", ".string.quoted.double.js"], "foo.bar.baz")).toBe 22 expect(syntax.getProperty([".source.js", ".variable.assignment.js"], "foo.bar.baz")).toBe 11 expect(syntax.getProperty([".text"], "foo.bar.baz")).toBe 1 + + it "favors the most recently added properties in the event of a specificity tie", -> + syntax.addProperties(".source.coffee .string.quoted.single", foo: bar: baz: 42) + syntax.addProperties(".source.coffee .string.quoted.double", foo: bar: baz: 22) + + expect(syntax.getProperty([".source.coffee", ".string.quoted.single"], "foo.bar.baz")).toBe 42 + expect(syntax.getProperty([".source.coffee", ".string.quoted.single.double"], "foo.bar.baz")).toBe 22 diff --git a/src/app/syntax.coffee b/src/app/syntax.coffee index bccf22e3c..f54b3f3da 100644 --- a/src/app/syntax.coffee +++ b/src/app/syntax.coffee @@ -7,38 +7,48 @@ module.exports = class Syntax constructor: -> @globalProperties = {} + @scopedPropertiesIndex = 0 + @scopedProperties = [] @propertiesBySelector = {} addProperties: (args...) -> - scopeSelector = args.shift() if args.length > 1 + selector = args.shift() if args.length > 1 properties = args.shift() - if scopeSelector - @propertiesBySelector[scopeSelector] ?= {} - _.extend(@propertiesBySelector[scopeSelector], properties) + if selector + @scopedProperties.unshift( + selector: selector, + properties: properties, + specificity: Specificity(selector), + index: @scopedPropertiesIndex++ + ) else _.extend(@globalProperties, properties) getProperty: (scope, keyPath) -> - for object in @propertiesForScope(scope) + for object in @propertiesForScope(scope, keyPath) value = _.valueForKeyPath(object, keyPath) return value if value? undefined - propertiesForScope: (scope) -> - matchingSelectors = [] - element = @buildScopeElement(scope) - while element - matchingSelectors.push(@matchingSelectorsForElement(element)...) - element = element.parentNode - properties = matchingSelectors.map (selector) => @propertiesBySelector[selector] - properties.concat([@globalProperties]) + propertiesForScope: (scope, keyPath) -> + matchingProperties = [] + candidates = @scopedProperties.filter ({properties}) -> _.valueForKeyPath(properties, keyPath)? + if candidates.length + element = @buildScopeElement(scope) + while element + matchingProperties.push(@matchingPropertiesForElement(element, candidates)...) + element = element.parentNode + matchingProperties.concat([@globalProperties]) - matchingSelectorsForElement: (element) -> - matchingSelectors = [] - for selector of @propertiesBySelector - matchingSelectors.push(selector) if jQuery.find.matchesSelector(element, selector) - matchingSelectors.sort (a, b) -> Specificity(b) - Specificity(a) + matchingPropertiesForElement: (element, candidates) -> + matchingScopedProperties = candidates.filter ({selector}) -> jQuery.find.matchesSelector(element, selector) + matchingScopedProperties.sort (a, b) -> + if a.specificity == b.specificity + b.index - a.index + else + b.specificity - a.specificity + _.pluck matchingScopedProperties, 'properties' buildScopeElement: (scope) -> scope = new Array(scope...)