From 08f2b8bdf416d2cad977827efcd59ffa80fe9782 Mon Sep 17 00:00:00 2001 From: Saq Imtiaz Date: Thu, 19 Feb 2026 12:14:19 +0100 Subject: [PATCH] Fixes variable enumeration in widgets (#9648) * fix: variable enumeration in widgets * fix: fakeWidget should have a variables property * fix: don't use spread properties * fix: resolve more embarassing bugs * chore: tests and whitespace * fix: simplify opts check --- core/modules/filterrunprefixes/cascade.js | 2 +- core/modules/filterrunprefixes/filter.js | 2 +- core/modules/filterrunprefixes/map.js | 2 +- core/modules/filterrunprefixes/reduce.js | 2 +- core/modules/filterrunprefixes/sort.js | 2 +- core/modules/filters/filter.js | 2 +- core/modules/filters/variables.js | 2 +- core/modules/widgets/widget.js | 71 +++++++++---------- .../tests/data/functions/VariableLeakage.tid | 15 ++++ .../data/functions/VariablesEnumeration.tid | 14 ++++ 10 files changed, 69 insertions(+), 45 deletions(-) create mode 100644 editions/test/tiddlers/tests/data/functions/VariableLeakage.tid create mode 100644 editions/test/tiddlers/tests/data/functions/VariablesEnumeration.tid diff --git a/core/modules/filterrunprefixes/cascade.js b/core/modules/filterrunprefixes/cascade.js index 98044579c..65e8a7cb8 100644 --- a/core/modules/filterrunprefixes/cascade.js +++ b/core/modules/filterrunprefixes/cascade.js @@ -24,7 +24,7 @@ exports.cascade = function(operationSubFunction,options) { } var output = filterFnList[index](options.wiki.makeTiddlerIterator([title]),widget.makeFakeWidgetWithVariables({ "currentTiddler": "" + title, - "..currentTiddler": widget.getVariable("currentTiddler","") + "..currentTiddler": widget.getVariable("currentTiddler",{defaultValue:""}) })); if(output.length !== 0) { result = output[0]; diff --git a/core/modules/filterrunprefixes/filter.js b/core/modules/filterrunprefixes/filter.js index 0c6ed150f..80ec590ec 100644 --- a/core/modules/filterrunprefixes/filter.js +++ b/core/modules/filterrunprefixes/filter.js @@ -18,7 +18,7 @@ exports.filter = function(operationSubFunction,options) { results.each(function(title) { var filtered = operationSubFunction(options.wiki.makeTiddlerIterator([title]),widget.makeFakeWidgetWithVariables({ "currentTiddler": "" + title, - "..currentTiddler": widget.getVariable("currentTiddler",""), + "..currentTiddler": widget.getVariable("currentTiddler",{defaultValue:""}), "index": "" + index, "revIndex": "" + (results.length - 1 - index), "length": "" + results.length diff --git a/core/modules/filterrunprefixes/map.js b/core/modules/filterrunprefixes/map.js index c7f1ae5eb..21225a1b5 100644 --- a/core/modules/filterrunprefixes/map.js +++ b/core/modules/filterrunprefixes/map.js @@ -20,7 +20,7 @@ exports.map = function(operationSubFunction,options) { $tw.utils.each(inputTitles,function(title) { var filtered = operationSubFunction(options.wiki.makeTiddlerIterator([title]),widget.makeFakeWidgetWithVariables({ "currentTiddler": "" + title, - "..currentTiddler": widget.getVariable("currentTiddler",""), + "..currentTiddler": widget.getVariable("currentTiddler",{defaultValue:""}), "index": "" + index, "revIndex": "" + (inputTitles.length - 1 - index), "length": "" + inputTitles.length diff --git a/core/modules/filterrunprefixes/reduce.js b/core/modules/filterrunprefixes/reduce.js index 351d976bd..65cb6af25 100644 --- a/core/modules/filterrunprefixes/reduce.js +++ b/core/modules/filterrunprefixes/reduce.js @@ -17,7 +17,7 @@ exports.reduce = function(operationSubFunction,options) { results.each(function(title) { var list = operationSubFunction(options.wiki.makeTiddlerIterator([title]),widget.makeFakeWidgetWithVariables({ "currentTiddler": "" + title, - "..currentTiddler": widget.getVariable("currentTiddler"), + "..currentTiddler": widget.getVariable("currentTiddler",{defaultValue:""}), "index": "" + index, "revIndex": "" + (results.length - 1 - index), "length": "" + results.length, diff --git a/core/modules/filterrunprefixes/sort.js b/core/modules/filterrunprefixes/sort.js index 126376887..bc60ab5fb 100644 --- a/core/modules/filterrunprefixes/sort.js +++ b/core/modules/filterrunprefixes/sort.js @@ -24,7 +24,7 @@ exports.sort = function(operationSubFunction,options) { results.each(function(title) { var key = operationSubFunction(options.wiki.makeTiddlerIterator([title]),widget.makeFakeWidgetWithVariables({ "currentTiddler": "" + title, - "..currentTiddler": widget.getVariable("currentTiddler") + "..currentTiddler": widget.getVariable("currentTiddler",{defaultValue:""}) })); sortKeys.push(key[0] || ""); }); diff --git a/core/modules/filters/filter.js b/core/modules/filters/filter.js index 466d43f8b..750b2ead2 100644 --- a/core/modules/filters/filter.js +++ b/core/modules/filters/filter.js @@ -19,7 +19,7 @@ exports.filter = function(source,operator,options) { source(function(tiddler,title) { var list = filterFn.call(options.wiki,options.wiki.makeTiddlerIterator([title]),options.widget.makeFakeWidgetWithVariables({ "currentTiddler": "" + title, - "..currentTiddler": options.widget.getVariable("currentTiddler","") + "..currentTiddler": options.widget.getVariable("currentTiddler",{defaultValue:""}) })); if((list.length > 0) === target) { results.push(title); diff --git a/core/modules/filters/variables.js b/core/modules/filters/variables.js index f35885cb0..a217133f5 100644 --- a/core/modules/filters/variables.js +++ b/core/modules/filters/variables.js @@ -24,4 +24,4 @@ exports.variables = function(source,operator,options) { } } return names.sort(); -}; +}; \ No newline at end of file diff --git a/core/modules/widgets/widget.js b/core/modules/widgets/widget.js index 864dbd514..e3a46653e 100755 --- a/core/modules/widgets/widget.js +++ b/core/modules/widgets/widget.js @@ -329,48 +329,43 @@ Widget.prototype.getStateQualifier = function(name) { /* Make a fake widget with specified variables, suitable for variable lookup in filters. Each variable can be a string or an array of strings */ -Widget.prototype.makeFakeWidgetWithVariables = function(variables) { - var self = this, - variables = variables || {}; - return { - getVariable: function(name,opts) { - if($tw.utils.hop(variables,name)) { - var value = variables[name]; - if($tw.utils.isArray(value)) { - return value[0]; - } else { - return value; - } - } else { - opts = opts || {}; - opts.variables = $tw.utils.extend({},variables,opts.variables); - return self.getVariable(name,opts); - }; +Widget.prototype.makeFakeWidgetWithVariables = function(vars = {}) { + const self = this; + + const fakeWidget = { + getVariableInfo(name,opts = {}) { + if(name in vars) { + const value = vars[name]; + return Array.isArray(value) + ? { text: value[0], resultList: value } + : { text: value, resultList: [value] }; + } + opts = opts || {}; + opts.variables = Object.assign({}, vars, opts.variables || {}); + return self.getVariableInfo(name, opts); }, - getVariableInfo: function(name,opts) { - if($tw.utils.hop(variables,name)) { - var value = variables[name]; - if($tw.utils.isArray(value)) { - return { - text: value[0], - resultList: value - }; - } else { - return { - text: value, - resultList: [value] - }; - } - } else { - opts = opts || {}; - opts.variables = $tw.utils.extend({},variables,opts.variables); - return self.getVariableInfo(name,opts); - }; + + + getVariable(name,opts) { + return this.getVariableInfo(name, opts).text; }, - makeFakeWidgetWithVariables: self.makeFakeWidgetWithVariables, + resolveVariableParameters: self.resolveVariableParameters, - wiki: self.wiki + wiki: self.wiki, + makeFakeWidgetWithVariables: self.makeFakeWidgetWithVariables, + + get variables() { + // Merge parent vars via prototype-like delegation + return Object.create(self.variables || {}, + Object.keys(vars).reduce((acc, key) => { + acc[key] = { value: vars[key], enumerable: true, configurable: true }; + return acc; + }, {}) + ); + } }; + + return fakeWidget; }; /* diff --git a/editions/test/tiddlers/tests/data/functions/VariableLeakage.tid b/editions/test/tiddlers/tests/data/functions/VariableLeakage.tid new file mode 100644 index 000000000..54becbd59 --- /dev/null +++ b/editions/test/tiddlers/tests/data/functions/VariableLeakage.tid @@ -0,0 +1,15 @@ +title: Functions/VariableLeakage +description: Variables from filter runs or functions should not pollute widget +type: text/vnd.tiddlywiki-multiple +tags: [[$:/tags/wiki-test-spec]] + +title: Output + +\function myvar(element) [] +\function call(element) [[myvar]is[variable]then] + +<> ++ +title: ExpectedResult + +

abc

\ No newline at end of file diff --git a/editions/test/tiddlers/tests/data/functions/VariablesEnumeration.tid b/editions/test/tiddlers/tests/data/functions/VariablesEnumeration.tid new file mode 100644 index 000000000..64ff5d149 --- /dev/null +++ b/editions/test/tiddlers/tests/data/functions/VariablesEnumeration.tid @@ -0,0 +1,14 @@ +title: Functions/VariableEnumeration +description: Variables should be enumerable within functions +type: text/vnd.tiddlywiki-multiple +tags: [[$:/tags/wiki-test-spec]] + +title: Output + +\function list-vars() [variables[]] + +<$text text={{{ [function[list-vars]count[]compare:number:gt[0]then[yes]] }}}/> ++ +title: ExpectedResult + +

yes

\ No newline at end of file