feat: project level context with rules using it - #1822
Draft
bhirsz wants to merge 4 commits into
Draft
Conversation
Introduce `robocop.project`, a package that collects data from the whole project instead of a single file. It is the foundation for rules that need to know about other files, such as unused keywords or keyword calls with an invalid number of arguments. Previously `run_project_checks()` did not parse anything - it only handed the `ConfigManager` to every checker, which then had to discover and parse files on its own. This made project rules impractical to write, which is why the existing `unused-keyword` rule was deprecated and disabled. The new `ProjectContext` is built once and passed to `ProjectChecker.scan_project`: - keyword definitions and usages indexed across the project, with support for embedded arguments and `Resource.Keyword` prefixes, - argument specifications parsed from the AST with `UserKeywordArgumentParser`, without importing or executing any code, - variables with Robot Framework compatible scoping, and imports resolved to real paths. Imports that use variables which cannot be resolved are marked as `UNRESOLVABLE` and never reported, so dynamic paths do not produce false positives. Variables can now be provided with `robocop check-project --variable NAME:VALUE` (matching `robot --variable`) or in the configuration file: ```toml [tool.robocop.variables] RESOURCE_DIR = "resources" ``` Add `unresolved-resource-import` (IMP07) as the first rule using the context. It is disabled by default and reports resource imports pointing to files that do not exist. `ProjectChecker.scan_project` gained an optional `context` argument. Custom checkers written against the old two argument signature are detected by inspecting the signature and are still called without the context. Also fix the project checks documentation, which described a signature and a `source` argument that never worked. Closes #1722 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add the two flagship project level rules built on top of `ProjectContext`.
`invalid-argument-count` (ARG08) compares the arguments used in a keyword call
with the `[Arguments]` setting of the keyword definition:
Login user # ARG08, expects 2 arguments but 1 provided
Argument specifications are parsed from the AST, so no library is imported and
no user code is executed. Only user keywords defined in the project are checked.
To keep false positives away, the call is skipped when the keyword name is built
from a variable, the keyword is not found or ambiguous, it uses embedded
arguments, the call expands a list or dictionary variable, or the keyword is
used as a test template.
Named arguments are resolved the way Robot Framework does it: `name=value` is
only a named argument when `name` matches an argument of the keyword or the
keyword accepts free named arguments. Otherwise it is a positional value.
The rule was validated against `robot --dryrun` on a fixture covering valid,
invalid and dynamic calls, and reports exactly the same set of errors.
`unused-keyword` (KW04) is no longer deprecated and is rewritten on top of the
project context. Previously it only worked for suites and private keywords and
was dead code, since `ProjectChecker` has no visiting machinery. It now reports
keywords that are not called anywhere in the project, including calls from test
setups, teardowns, templates and keywords nested in `Run Keyword` variants.
Keywords tagged with `robot:private` are only matched against calls from the
file they are defined in. Calls with a name built from a variable are turned
into a pattern, so `Login ${type}` marks both `Login Admin` and `Login User` as
used instead of reporting them.
Both rules are disabled by default and have to be selected explicitly.
Supporting changes:
- `iterate_keyword_calls()` returns each keyword name together with the
arguments belonging to that particular call. This is required for nested run
keywords, where the arguments of the statement are not the arguments of the
called keyword.
- `ProjectContext.visible_keywords()` resolves keywords through transitive
resource imports instead of a flat project wide index, so same named keywords
in unrelated files are not confused with each other.
- BDD prefixes are recognized, so `Given The User Logs In` also matches a
keyword named `The User Logs In`.
- The project context is no longer built when no enabled checker uses it.
Parsing the whole project on every `check-project` run was wasted work now
that all project rules are opt-in.
The previously skipped `unused-keyword` acceptance tests are enabled again. Its
expected output gained one keyword, which is defined in a resource file and
never called - the exact case the rule could not detect before.
Closes #1017
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ules Extend the project context with variable usages so that project level rules can tell whether anything provided by an import is actually used. New rules: - IMP05 `unused-resource-import` reports resource imports whose keywords and variables are never used. Resource imports are transitive, so usages are looked up in every file that can see the import, and resources providing nothing on their own are never reported. Files calling keywords by a name built from a variable are skipped to avoid false positives. - DUP11 `duplicated-variable-in-project` reports the same variable defined in multiple files visible together through imports, which makes the value used at runtime depend on the import order. Both rules are disabled by default and run with `robocop check-project`. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Finding the files that see a resource import walked the transitive import closure of every file for every checked file. Build the reverse index once per run instead. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds project level context to the linter and the first rules built on top of it. This is the foundation discussed in #1677, where the direction was to investigate how
robotcodesolves this. Implemented self-contained, without depending on therobotcodeLSP stack.Still a draft - more rules are planned on this branch.
Problem
run_project_checks()never parsed anything. It only handed theConfigManagerto each checker, which then had torgloband parse files on its own. Writing a project rule meant reimplementing discovery, parsing and import resolution every time.That is why
unused-keywordwas deprecated and disabled: it definesvisit_File, butProjectCheckerhas no visiting machinery, so it was effectively dead code.Project context
A
ProjectContextis now built once and passed toProjectChecker.scan_project:Resource.Keywordprefixes and BDD prefixes.UserKeywordArgumentParser, so no library is imported and no user code is executed.*** Variables ***> imported > builtin).robot.variables.Variables.replace_string, so%{ENV}, nesting and escaping behave exactly like in Robot Framework.Imports using variables that cannot be resolved are marked
UNRESOLVABLEand are never reported. This is the main safety valve against false positives on dynamic paths.The context is only built when an enabled checker actually uses it, so a run without project rules does not pay for parsing the project.
Configuration
Variables can be provided the same way as in Robot Framework:
or in the configuration file:
Variable names are no longer normalized, so
MY-VARstaysMY-VAR. Previouslynormalize_config_keysrewrote-to_recursively, which would have silently corrupted user defined variable names.New rules
All of them are disabled by default and have to be selected explicitly.
invalid-argument-count(ARG08)Compares the arguments used in a call with the
[Arguments]of the definition.Named arguments are resolved the way Robot Framework does it:
name=valueis only named whennamematches an argument of the keyword or the keyword accepts free named arguments, otherwise it is a positional value.Skipped to avoid false positives: dynamic keyword names, keywords not found or ambiguous, embedded arguments,
@{list}/&{dict}expansion, and template keywords.unused-keyword(KW04)Un-deprecated and rewritten. Reports keywords never called anywhere in the project, taking into account setups, teardowns, templates and
Run Keywordvariants.Private keywords are only matched against calls from their own file. Calls with a name built from a variable become a pattern, so
Login ${type}marks bothLogin AdminandLogin Useras used.unused-resource-import(IMP05)Reports resource imports whose keywords and variables are never used.
Resource imports are transitive in Robot Framework, so an import is checked against the usages of every file that can see it. A resource imported only to re-export its own imports is therefore not reported, and neither is a resource that defines no keywords and no variables.
Skipped to avoid false positives: unresolved imports and files calling keywords by a name built from a variable.
duplicated-variable-in-project(DUP11)Reports the same variable defined in several files that are visible together through imports.
Robot Framework does not complain about this, but the value used at runtime depends on the import order, which makes it a common source of hard to debug problems. Names are normalized, so
${my var},${MY_VAR}and${myvar}are the same variable.unresolved-resource-import(IMP07)Reports resource imports pointing to files that do not exist.
Backwards compatibility
ProjectChecker.scan_projectgained an optional thirdcontextargument. Custom checkers written against the old two argument signature are detected by inspecting the signature and are still called without the context.Signature inspection is used rather than
try/except TypeError, so aTypeErrorraised inside a checker is not silently misinterpreted as an old signature.Docs
The project checks documentation described a
scan_projectsignature and asourceargument that never worked. Rewritten and corrected, which closes #1722.Follow-up
Planned next, not in this PR yet:
unused-library-import(Rule Request: Unused resource #1780), limited to standard libraries until library introspection landsambiguous-keyword-name,keyword-not-foundTest plan
invalid-argument-countagainstrobot --dryrunon a fixture with valid, invalid, nested and dynamic calls - Robocop reports exactly the same set of errors, no more and no less.unused-keywordacceptance tests are enabled again. Expected output gained one keyword defined in a resource file and never called, which is the exact case the old rule could not detect.--variableparsing and config key normalization.--variablemakes the import resolve.main).ruff check/ruff formatclean;mypyunchanged from baseline (7 pre-existing errors in untouched files).Closes #1017
Closes #1119
Closes #1780
Closes #1722