Skip to content

VIEW015: unused-custom-property

Custom property or view parameter is never read

Code VIEW015
Name unused-custom-property
Category View structure (VIEW)
Default severity warning
Enabled by default yes
Fix available sometimes
Fix safety safe or unsafe, depending on the finding (see below)
ign-lint equivalent UnusedCustomPropertiesRule

Reports custom properties (of the view and of its components) and view input parameters that nothing in the view reads.

A property counts as read when it is resolved by an exact reference, not by a text search:

  • {...} references in any expression of the view (expression bindings, expression transforms, expression-structure leaves, query parameters, tag expressions, map transforms), resolved against the component tree ({this.custom.x}, {view.params.p}, {../Card.custom.x}, {/root/Card.custom.x}); {view.custom} reads every view custom property;
  • runScript in an expression of the view runs its code with self, view, page and session: code that names any of them, or a computed script, counts as reading and writing every property of the view;
  • property binding paths and indirect tag binding references;
  • scripts of the view (events, transforms, change scripts, message handlers, custom methods, extension functions), parsed with the Jython parser. The analysis is default-deny: a component, the view, a page or session, a property container (self.custom, self.view.params) or a local alias of one must be used in one of the enumerated forms (self.view.custom.x, self.custom['x'], self.custom.get('x'), 'x' in self.custom, self.getSibling('A').custom.x, self.parent.getChild('A').custom.x, view = self.view, an argument of a library function, a custom method of the view, ...); any other use (passed to unknown code, iterated, stored, compared, formatted, self.custom.keys used without a call, self.custom[key], a script that does not parse) counts as reading and writing every property it could reach, so the rule stays silent rather than guess. The list of forms is in docs/internal/fix-safety.md;
  • builtins that read the caller's variables by name: a script that calls locals(), vars(), dir(), eval or execfile, runs exec, reaches frames (sys._getframe(), inspect.currentframe(), .f_locals) or reaches such a builtin in any other way (__builtin__.eval, from __builtin__ import locals as l, e = eval) reads everything. Every library name a script calls or uses as a value must be a plain def function (or class, or literal constant), or not exist: a library variable or import may hold eval under another name (ev = eval in a module, lib.util.ev('self.custom.x') reads the caller's self);
  • project library functions a script passes self, the view or a property container to (lib.util.handle(self), handle(view=self.view)): the function's body is followed with the same default-deny rules (param.view.custom.x, x = param.custom.y even when x is never used, param.getSibling('A').custom.y, passing it on to other library functions, up to 4 calls deep). A function that does not exist reads nothing (the call fails, which REF002 reports). A function that uses the value in a way that cannot be followed (it stores the value where it outlives the call, in a global, a module or class attribute, a default argument or a container it does not own, returns, yields or raises it, captures it in a nested function, or reaches stack frames with sys._getframe() or inspect), a function a child project overrides, a function that may be replaced at run time (its module runs exec, eval or execfile, writes globals() or sys.modules[__name__], sets its func_code, or may overwrite it with a later definition or from x import *; a script assigns or setattrs it; its module is used as a value, imported with importlib.import_module, reached through sys.modules or code run from strings; a function or class of its module is used as a value anywhere (alias = handle, register(handle)) or its func_globals is reached), and calls of anything but a library function count as reading everything.

These never count as unused:

  • properties with a binding or a change script (propConfig binding or onChange): the binding or script runs whether or not anything reads the value;
  • output parameters (whether they are written is VIEW019's concern); inout parameters that the view writes;
  • names starting with _, the convention for intentionally unused properties.

Input parameters set by an embedding view or a page URL are still reported: passing a value in does not make the view use it.

Unused properties are dead configuration: readers have to work out that nothing depends on them, and unused parameters make callers pass values that are ignored.

{"custom": {"unusedCounter": 0, "title": "Pumps"},
"root": {"type": "ia.display.label", "meta": {"name": "root"},
"propConfig": {"props.text": {"binding": {"type": "property", "config": {"path": "view.custom.title"}}}}}}

unusedCounter is reported. Use instead: remove it.

For custom properties: removes the property and its propConfig entries. Safe when nothing writes the property and the view's own scripts prove it unread, without following values into library functions. Unsafe when a script or a bidirectional binding writes it (the write recreates the property, without the access and persistent settings of its propConfig), when the finding relies on what a library function does with a value of the view (code in other resources, which other projects or run-time changes can invalidate), and for a view linted outside its project whose scripts call names that may be library functions. View parameters are never removed automatically (callers pass them).

Like every fix, it is unsafe while code in the workspace may read the files a fix edits (any file access, read, write, rename or delete, whose path is not a literal outside the workspace, or code that is not vetted); the project setting [fix] assume-scripts-do-not-read-workspace-files = true declares that no script does (see docs/internal/fix-safety.md, "File reads").

Equivalent of UnusedCustomPropertiesRule, with the same messages in iglint ign-lint. ign-lint searches substrings, so view.custom.foo is credited by {view.custom.foobar}, this.custom.x anywhere credits every component's x, same-named components share one definition, and component propConfig entries become bogus definitions (bugs 21 to 23). iglint resolves every reference instead.


Generated from the rule metadata by iglint generate-docs; iglint rule VIEW015 prints the same page in the terminal. Source: docs/rules/VIEW015.md.