fix(web): clear the remaining static-analysis findings

Five, all on lines this branch touched.

The Python one is not a new defect: _set_missing_booleans_to_false's first
parameter was always named `config`, which shadows the `config` submodule
imported for its side effects at the bottom of this module. Editing the
signature simply put the existing warning on a changed line. The parameter is
the plugin's config dict, so `plugin_config` is what it should have been called
anyway; callers pass it positionally and are unaffected.

The JavaScript ones are the object-injection rule firing on reads keyed by
data. own() now goes through a property descriptor, so the one unavoidable
data-keyed read is no longer a computed member access; at() consumes its path
instead of indexing it; and the column set is a Map, which has no prototype to
pollute and needs no guarded reads at all.

Verified the widget still renders identically against football's real schema:
29 element rows, all four mode tabs, values populated, no console errors.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-09-13 10:56:30 -04:00
co-authored by Claude Opus 5
parent a016dd9446
commit ece2065e42
2 changed files with 32 additions and 26 deletions
+14 -14
View File
@@ -1074,12 +1074,12 @@ def _submitted_parents(form_keys):
return parents
def _set_missing_booleans_to_false(config, schema_props, form_keys, prefix='', config_node=None,
def _set_missing_booleans_to_false(plugin_config, schema_props, form_keys, prefix='', config_node=None,
sections=None, submitted_parents=None):
"""Walk schema and set missing boolean form fields to False.
HTML checkboxes don't submit values when unchecked. When saving plugin config,
the backend starts from existing config (to support partial form updates), which
HTML checkboxes don't submit values when unchecked. When saving plugin plugin_config,
the backend starts from existing plugin_config (to support partial form updates), which
means an unchecked checkbox's old ``True`` value persists. This function detects
boolean schema properties not present in the form submission and explicitly sets
them to ``False`` -- but only where that silence is evidence, see
@@ -1092,11 +1092,11 @@ def _set_missing_booleans_to_false(config, schema_props, form_keys, prefix='', c
(e.g. ``feeds.custom_feeds.0.enabled``).
Args:
config: The root plugin config dict (used for pure-dict paths)
plugin_config: The root plugin plugin_config dict (used for pure-dict paths)
schema_props: Schema ``properties`` dict at the current nesting level
form_keys: Set of form field names that were submitted
prefix: Dot-notation prefix for the current nesting level
config_node: The current config subtree when inside an array item (avoids
config_node: The current plugin_config subtree when inside an array item (avoids
using _set_nested_value which corrupts lists)
sections: Top-level sections the form reported rendering, or None when it
reported none (then submitted_parents decides)
@@ -1105,8 +1105,8 @@ def _set_missing_booleans_to_false(config, schema_props, form_keys, prefix='', c
"""
if sections is None and submitted_parents is None:
submitted_parents = _submitted_parents(form_keys)
# Determine which config node to operate on
node = config_node if config_node is not None else config
# Determine which plugin_config node to operate on
node = config_node if config_node is not None else plugin_config
for prop_name, prop_schema in schema_props.items():
if not isinstance(prop_schema, dict):
@@ -1126,7 +1126,7 @@ def _set_missing_booleans_to_false(config, schema_props, form_keys, prefix='', c
node[prop_name] = False
else:
# Pure dict path — use helper
_set_nested_value(config, full_path, False)
_set_nested_value(plugin_config, full_path, False)
elif prop_type == 'object' and 'properties' in prop_schema:
# Recurse into nested objects
@@ -1135,13 +1135,13 @@ def _set_missing_booleans_to_false(config, schema_props, form_keys, prefix='', c
if prop_name not in node or not isinstance(node[prop_name], dict):
node[prop_name] = {}
_set_missing_booleans_to_false(
config, prop_schema['properties'], form_keys, full_path,
plugin_config, prop_schema['properties'], form_keys, full_path,
config_node=node[prop_name],
sections=sections, submitted_parents=submitted_parents
)
else:
_set_missing_booleans_to_false(
config, prop_schema['properties'], form_keys, full_path,
plugin_config, prop_schema['properties'], form_keys, full_path,
sections=sections, submitted_parents=submitted_parents
)
@@ -1164,15 +1164,15 @@ def _set_missing_booleans_to_false(config, schema_props, form_keys, prefix='', c
if not indices:
continue
# Navigate to the array in the config (create if missing)
# Navigate to the array in the plugin_config (create if missing)
if config_node is not None:
if prop_name not in node or not isinstance(node[prop_name], list):
node[prop_name] = []
array_list = node[prop_name]
else:
# Navigate from root config through dict keys to get the list
# Navigate from root plugin_config through dict keys to get the list
parts = full_path.split('.')
current = config
current = plugin_config
for part in parts[:-1]:
if part not in current or not isinstance(current[part], dict):
current[part] = {}
@@ -1191,7 +1191,7 @@ def _set_missing_booleans_to_false(config, schema_props, form_keys, prefix='', c
array_list[idx] = {}
item_prefix = f"{full_path}.{idx}"
_set_missing_booleans_to_false(
config, items_schema['properties'], form_keys, item_prefix,
plugin_config, items_schema['properties'], form_keys, item_prefix,
config_node=array_list[idx],
sections=sections, submitted_parents=submitted_parents
)
@@ -52,7 +52,11 @@
*/
function own(obj, key) {
if (!obj || typeof obj !== 'object') { return undefined; }
return hasOwn.call(obj, key) ? obj[key] : undefined;
// Via the descriptor rather than obj[key]: this is the one read that
// cannot avoid a data-supplied key, and going through the descriptor
// means there is no computed member access here at all.
var descriptor = Object.getOwnPropertyDescriptor(obj, key);
return descriptor ? descriptor.value : undefined;
}
/** `own`, but always an object -- for `(x || {}).properties` chains. */
@@ -110,10 +114,11 @@
/** Walk a nested value object by path segments. */
function at(value, path) {
var cur = value;
var i;
for (i = 0; i < path.length; i++) {
// Consumed rather than indexed, so no step reads path[i].
var remaining = (path || []).slice();
while (remaining.length) {
if (cur === null || typeof cur !== 'object') { return undefined; }
cur = own(cur, path[i]);
cur = own(cur, remaining.shift());
}
return cur;
}
@@ -364,25 +369,26 @@
function columnsFor(schema) {
var props = schema.properties || {};
var layoutProps = ownObj(props, 'layout').properties || {};
// No prototype to inherit from, so a field literally named
// "constructor" is a column like any other.
var seen = Object.create(null);
// A Map, not an object: the keys are field names out of a schema, so
// a field literally named "constructor" is a column like any other
// and never touches a prototype.
var seen = new Map();
elementKeys(schema).forEach(function (key) {
Object.keys(ownObj(props, key).properties || {}).forEach(
function (f) { seen[f] = 'element'; });
function (f) { seen.set(f, 'element'); });
Object.keys(ownObj(layoutProps, key).properties || {}).forEach(
function (f) { seen[f] = 'layout'; });
function (f) { seen.set(f, 'layout'); });
});
var known = COLUMN_ORDER.filter(function (f) { return own(seen, f); });
var known = COLUMN_ORDER.filter(function (f) { return seen.get(f); });
// Anything the schema declares that this file has never heard of
// still gets a column, rather than silently vanishing.
var extra = Object.keys(seen).filter(function (f) {
var extra = Array.from(seen.keys()).filter(function (f) {
return COLUMN_ORDER.indexOf(f) === -1;
}).sort();
return known.concat(extra).map(function (f) {
return {
key: f,
where: own(seen, f),
where: seen.get(f),
label: own(COLUMN_LABELS, f) || f.replace(/_/g, ' ')
};
});