') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); fix(string): preserve falsy variable values in template() by maxtaran2010 · Pull Request #56 · antfu/utils · GitHub
Skip to content

fix(string): preserve falsy variable values in template() - #56

Open
maxtaran2010 wants to merge 1 commit into
antfu:mainfrom
maxtaran2010:fix/template-falsy-values
Open

fix(string): preserve falsy variable values in template()#56
maxtaran2010 wants to merge 1 commit into
antfu:mainfrom
maxtaran2010:fix/template-falsy-values

Conversation

@maxtaran2010

Copy link
Copy Markdown

Summary

The template() function uses vars[key] || fallback when doing named substitution, which causes it to fall through to the fallback value whenever a variable is explicitly set to a falsy value — 0, false, or "".

Buggy behavior:

template('{count}',{count: 0},'N/A')// returns 'N/A', expected '0'template('{active}',{active: false},'unknown')// returns 'unknown', expected 'false'template('{name}',{name: ''},'anonymous')// returns 'anonymous', expected ''

Fix

Replace vars[key] || fallback with key in vars ? vars[key] : fallback so only keys that are actually absent from vars trigger the fallback, not keys that are present but hold a falsy value.

- return str.replace(/\{(\w+)\}/g, (_, key) => vars[key] || ((typeof fallback === 'function' ? fallback(key) : fallback) ?? key))+ return str.replace(/\{(\w+)\}/g, (_, key) => key in vars ? vars[key] : ((typeof fallback === 'function' ? fallback(key) : fallback) ?? key))

Test plan

  • Added test cases for 0, false, and "" values in named template mode
  • All existing tests continue to pass (pnpm test — 39 tests passing)

…le values
`vars[key] || fallback` incorrectly falls through to the fallback when
a variable is explicitly set to a falsy value like 0, false, or "".
Switch to `key in vars ? vars[key] : fallback` so only missing keys
trigger the fallback, not legitimate falsy values.
Adds test cases for 0, false, and "" to prevent regression.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CopilotAI review requested due to automatic review settings July 9, 2026 21:07
CopilotAI reviewed Jul 9, 2026

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Comment threadsrc/string.ts
if (isObject(firstArg)) {
const vars = firstArg as Record<string, any>
return str.replace(/\{(\w+)\}/g, (_, key) => vars[key] || ((typeof fallback === 'function' ? fallback(key) : fallback) ?? key))
return str.replace(/\{(\w+)\}/g, (_, key) => key in vars ? vars[key] : ((typeof fallback === 'function' ? fallback(key) : fallback) ?? key))

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of in, let's use "hasOwn" to be safer?

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@maxtaran2010@antfu