get-token returns key string when $handle-missing-keys = 'null'
johncrim opened this issue · comments
Given the following 2 tests, the first test fails, and the second test passes:
$map: (
'foo': (
'baz': 2
)
)
@include it('Returns null when root key missing if null mode is enabled') {
$handle-missing-keys: 'null' !global;
@include assert-equal(
get-token($map, 'missing'),
null
);
$handle-missing-keys: 'silent' !global;
}
@include it('Returns null when sub-key missing if null mode is enabled') {
$handle-missing-keys: 'null' !global;
@include assert-equal(
get-token($map, 'foo->missing'),
null
);
$handle-missing-keys: 'silent' !global;
}
It appears that the code path is different for root keys vs nested keys. Since I have a test, I'll attempt to provide a fix.
I dug into this in hopes of providing a fix, and to be frank it looks like a proper fix requires a significant refactor. Right now all the $handle-missing-keys
logic is in _a_replace
, and that code path isn't touched when the key is missing altogether.
The code is pretty difficult to follow and reason about due to conflated responsibilities - eg _a_replace
does a lot more than string replacement, and _a_parse
does a lot more than parsing a key string or list or map. Both functions return resolved values, eg what one would expect from _a_get
. If I were to refactor this, my goal would be to focus _a_parse
so it purely parses the key into a data structure that can be resolved in a straightforward manner (like a list of individual keys/references) without referencing the map, and _a_get
would resolve that data structure to values in the map, and handle missing values in one place. That's probably more than you want to fix this bug.
On the positive side, the extensive unit tests are great for protecting against breaks in existing behavior. I really appreciate this project and how it enables front-end developers to work, so please take that criticism as intended to be helpful.
It might be possible to detect this case without the refactor, but it's more challenging than one would think, because recursive resolution is central to the expected behavior (eg other internal code calls a.get-token()
and expects the key to be returned when there is no match). It's also interesting that it only fails on the missing root key case, if there are 2 keys (eg missing->foo
) null is returned.
To workaround this issue for now, I will detect both null returned and key returned as meaning "value not found".
This probably isn't worth fixing until the migration to use sass's nested maps functions. At that point I would expect a refactor that separates parsing from resolution, so it would be easy to handle missing values in a single location: within get-token() (and not within the parsing functions).
@johncrim Thanks for tracking that down. Are you interested in helping with that sass modules refactor? I opened a branch a while back, but never had the time to get very far.
Hi @mirisuzanne - yes, I'm interested in helping. I'm working nights and weekend on my startup, so time may be an issue, but I do think I can provide significant help here.
I think the primary reason this happens is that we currently allow the $key
argument to accept either a token key, or a token to parse for internal aliases & adjustments. Since we allow both, it's impossible to know if the key is "missing" or if it wasn't intended as a key in the first place.
I think, moving forward (if we keep the parser at all), those should be distinct functions.
token.get()
accepts a map and key, and respects missing-key configtoken.compile()
accepts a map and arbitrary token to be compiled or returned as-is
fixed (and possibly to-be-removed) in the modules upgrade.