Return a cloned object from config.get()

This prevents mutations to the values of the default settings.
This commit is contained in:
Kevin Sawicki & Nathan Sobo
2013-05-16 10:27:51 -07:00
parent 097ee9f2a9
commit 2b49a04227
6 changed files with 45 additions and 27 deletions

View File

@@ -48,11 +48,6 @@ the following way:
```coffeescript
# basic key update
config.set("core.autosave", true)
# if you mutate a config key, you'll need to call `config.update` to inform
# observers of the change
config.get("fuzzyFinder.ignoredPaths").push "vendor"
config.update()
```
You can also use `setDefaults`, which will assign default values for keys that

View File

@@ -8,6 +8,13 @@ describe "Config", ->
expect(config.get("foo.bar.baz")).toBe 42
expect(config.get("bogus.key.path")).toBeUndefined()
it "returns a deep clone of the key path's value", ->
config.set('value', array: [1, b: 2, 3])
retrievedValue = config.get('value')
retrievedValue.array[0] = 4
retrievedValue.array[1].b = 2.1
expect(config.get('value')).toEqual(array: [1, b: 2, 3])
describe ".set(keyPath, value)", ->
it "allows a key path's value to be written", ->
expect(config.set("foo.bar.baz", 42)).toBe 42
@@ -93,21 +100,6 @@ describe "Config", ->
expect(config.get("foo.quux.x")).toBe 0
expect(config.get("foo.quux.y")).toBe 1
describe ".update()", ->
it "updates observers if a value is mutated without the use of .set", ->
config.set("foo.bar.baz", ["a"])
observeHandler = jasmine.createSpy "observeHandler"
config.observe "foo.bar.baz", observeHandler
observeHandler.reset()
config.get("foo.bar.baz").push("b")
config.update()
expect(observeHandler).toHaveBeenCalledWith config.get("foo.bar.baz")
observeHandler.reset()
config.update()
expect(observeHandler).not.toHaveBeenCalled()
describe ".observe(keyPath)", ->
observeHandler = null

View File

@@ -180,8 +180,9 @@ describe "Project", ->
it "ignores ignored.txt file", ->
paths = null
config.get("core.ignoredNames").push("ignored.txt")
config.update()
ignoredNames = config.get("core.ignoredNames")
ignoredNames.push("ignored.txt")
config.set("core.ignoredNames", ignoredNames)
waitsForPromise ->
project.getFilePaths().done (foundPaths) -> paths = foundPaths

View File

@@ -106,8 +106,8 @@ class Config
# Returns the value from Atom's default settings, the user's configuration file,
# or `null` if the key doesn't exist in either.
get: (keyPath) ->
_.valueForKeyPath(@settings, keyPath) ?
_.valueForKeyPath(@defaultSettings, keyPath)
value = _.valueForKeyPath(@settings, keyPath) ? _.valueForKeyPath(@defaultSettings, keyPath)
_.deepClone(value)
# Retrieves the setting for the given key as an integer.
#

View File

@@ -29,11 +29,12 @@ class PackageConfigPanel extends ConfigPanel
@on 'change', '#packages input[type=checkbox]', (e) ->
checkbox = $(e.target)
name = checkbox.closest('tr').attr('name')
disabledPackages = config.get('core.disabledPackages')
if checkbox.attr('checked')
_.remove(config.get('core.disabledPackages'), name)
_.remove(disabledPackages, name)
else
config.get('core.disabledPackages').push(name)
config.update()
disabledPackages.push(name)
config.set('core.disabledPackages', disabledPackages)
@observeConfig 'core.disabledPackages', (disabledPackages) =>
@packageTableBody.find("input[type='checkbox']").attr('checked', true)

View File

@@ -126,6 +126,35 @@ _.mixin
endsWith: (string, suffix) ->
string.indexOf(suffix, string.length - suffix.length) isnt -1
# Transform the given object into another object.
#
# `object` - The object to transform.
# `iterator` -
# A function that takes `(key, value)` arguments and returns a
# `[key, value]` tuple.
#
# Returns a new object based with the key/values returned by the iterator.
mapObject: (object, iterator) ->
newObject = {}
for key, value of object
[key, value] = iterator(key, value)
newObject[key] = value
newObject
# Deep clones the given JSON object.
#
# `object` - The JSON object to clone.
#
# Returns a deep clone of the JSON object.
deepClone: (object) ->
if _.isArray(object)
object.map (value) -> _.deepClone(value)
else if _.isObject(object)
@mapObject object, (key, value) => [key, @deepClone(value)]
else
object
valueForKeyPath: (object, keyPath) ->
keys = keyPath.split('.')
for key in keys