mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-13 18:46:37 +00:00
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:
@@ -1074,12 +1074,12 @@ def _submitted_parents(form_keys):
|
|||||||
return parents
|
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):
|
sections=None, submitted_parents=None):
|
||||||
"""Walk schema and set missing boolean form fields to False.
|
"""Walk schema and set missing boolean form fields to False.
|
||||||
|
|
||||||
HTML checkboxes don't submit values when unchecked. When saving plugin config,
|
HTML checkboxes don't submit values when unchecked. When saving plugin plugin_config,
|
||||||
the backend starts from existing config (to support partial form updates), which
|
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
|
means an unchecked checkbox's old ``True`` value persists. This function detects
|
||||||
boolean schema properties not present in the form submission and explicitly sets
|
boolean schema properties not present in the form submission and explicitly sets
|
||||||
them to ``False`` -- but only where that silence is evidence, see
|
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``).
|
(e.g. ``feeds.custom_feeds.0.enabled``).
|
||||||
|
|
||||||
Args:
|
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
|
schema_props: Schema ``properties`` dict at the current nesting level
|
||||||
form_keys: Set of form field names that were submitted
|
form_keys: Set of form field names that were submitted
|
||||||
prefix: Dot-notation prefix for the current nesting level
|
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)
|
using _set_nested_value which corrupts lists)
|
||||||
sections: Top-level sections the form reported rendering, or None when it
|
sections: Top-level sections the form reported rendering, or None when it
|
||||||
reported none (then submitted_parents decides)
|
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:
|
if sections is None and submitted_parents is None:
|
||||||
submitted_parents = _submitted_parents(form_keys)
|
submitted_parents = _submitted_parents(form_keys)
|
||||||
# Determine which config node to operate on
|
# Determine which plugin_config node to operate on
|
||||||
node = config_node if config_node is not None else config
|
node = config_node if config_node is not None else plugin_config
|
||||||
|
|
||||||
for prop_name, prop_schema in schema_props.items():
|
for prop_name, prop_schema in schema_props.items():
|
||||||
if not isinstance(prop_schema, dict):
|
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
|
node[prop_name] = False
|
||||||
else:
|
else:
|
||||||
# Pure dict path — use helper
|
# 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:
|
elif prop_type == 'object' and 'properties' in prop_schema:
|
||||||
# Recurse into nested objects
|
# 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):
|
if prop_name not in node or not isinstance(node[prop_name], dict):
|
||||||
node[prop_name] = {}
|
node[prop_name] = {}
|
||||||
_set_missing_booleans_to_false(
|
_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],
|
config_node=node[prop_name],
|
||||||
sections=sections, submitted_parents=submitted_parents
|
sections=sections, submitted_parents=submitted_parents
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
_set_missing_booleans_to_false(
|
_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
|
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:
|
if not indices:
|
||||||
continue
|
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 config_node is not None:
|
||||||
if prop_name not in node or not isinstance(node[prop_name], list):
|
if prop_name not in node or not isinstance(node[prop_name], list):
|
||||||
node[prop_name] = []
|
node[prop_name] = []
|
||||||
array_list = node[prop_name]
|
array_list = node[prop_name]
|
||||||
else:
|
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('.')
|
parts = full_path.split('.')
|
||||||
current = config
|
current = plugin_config
|
||||||
for part in parts[:-1]:
|
for part in parts[:-1]:
|
||||||
if part not in current or not isinstance(current[part], dict):
|
if part not in current or not isinstance(current[part], dict):
|
||||||
current[part] = {}
|
current[part] = {}
|
||||||
@@ -1191,7 +1191,7 @@ def _set_missing_booleans_to_false(config, schema_props, form_keys, prefix='', c
|
|||||||
array_list[idx] = {}
|
array_list[idx] = {}
|
||||||
item_prefix = f"{full_path}.{idx}"
|
item_prefix = f"{full_path}.{idx}"
|
||||||
_set_missing_booleans_to_false(
|
_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],
|
config_node=array_list[idx],
|
||||||
sections=sections, submitted_parents=submitted_parents
|
sections=sections, submitted_parents=submitted_parents
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -52,7 +52,11 @@
|
|||||||
*/
|
*/
|
||||||
function own(obj, key) {
|
function own(obj, key) {
|
||||||
if (!obj || typeof obj !== 'object') { return undefined; }
|
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. */
|
/** `own`, but always an object -- for `(x || {}).properties` chains. */
|
||||||
@@ -110,10 +114,11 @@
|
|||||||
/** Walk a nested value object by path segments. */
|
/** Walk a nested value object by path segments. */
|
||||||
function at(value, path) {
|
function at(value, path) {
|
||||||
var cur = value;
|
var cur = value;
|
||||||
var i;
|
// Consumed rather than indexed, so no step reads path[i].
|
||||||
for (i = 0; i < path.length; i++) {
|
var remaining = (path || []).slice();
|
||||||
|
while (remaining.length) {
|
||||||
if (cur === null || typeof cur !== 'object') { return undefined; }
|
if (cur === null || typeof cur !== 'object') { return undefined; }
|
||||||
cur = own(cur, path[i]);
|
cur = own(cur, remaining.shift());
|
||||||
}
|
}
|
||||||
return cur;
|
return cur;
|
||||||
}
|
}
|
||||||
@@ -364,25 +369,26 @@
|
|||||||
function columnsFor(schema) {
|
function columnsFor(schema) {
|
||||||
var props = schema.properties || {};
|
var props = schema.properties || {};
|
||||||
var layoutProps = ownObj(props, 'layout').properties || {};
|
var layoutProps = ownObj(props, 'layout').properties || {};
|
||||||
// No prototype to inherit from, so a field literally named
|
// A Map, not an object: the keys are field names out of a schema, so
|
||||||
// "constructor" is a column like any other.
|
// a field literally named "constructor" is a column like any other
|
||||||
var seen = Object.create(null);
|
// and never touches a prototype.
|
||||||
|
var seen = new Map();
|
||||||
elementKeys(schema).forEach(function (key) {
|
elementKeys(schema).forEach(function (key) {
|
||||||
Object.keys(ownObj(props, key).properties || {}).forEach(
|
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(
|
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
|
// Anything the schema declares that this file has never heard of
|
||||||
// still gets a column, rather than silently vanishing.
|
// 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;
|
return COLUMN_ORDER.indexOf(f) === -1;
|
||||||
}).sort();
|
}).sort();
|
||||||
return known.concat(extra).map(function (f) {
|
return known.concat(extra).map(function (f) {
|
||||||
return {
|
return {
|
||||||
key: f,
|
key: f,
|
||||||
where: own(seen, f),
|
where: seen.get(f),
|
||||||
label: own(COLUMN_LABELS, f) || f.replace(/_/g, ' ')
|
label: own(COLUMN_LABELS, f) || f.replace(/_/g, ' ')
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user