-
Notifications
You must be signed in to change notification settings - Fork 19
feat(linter): add linter service framework #907
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat(linter): add linter service framework #907
Conversation
lengau
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! I've done an initial review with a few things that need changing.
|
Hi @HamdaanAliQuatil, thanks for taking this on. :) I'm holding off my documentation review until the implementation is complete. If it looks like the implementation is a ways off, it might be better to split the documentation portion into its own PR afterward. |
lengau
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good so far!
Please add a couple of linters to testcraft and a spread test that tests for the app outputting the expected values.
A good pre linter would be a recommended field (e.g. version) being missing. A good post linter would be that the artifact is empty other than the generated metadata.yaml file.
32e3a0e to
8fdcaf9
Compare
|
@lengau spread tests in |
|
@HamdaanAliQuatil I've merged from main and re-triggered the workflows. Please address the failure with the lint command in testcraft, but otherwise I'm pretty happy with this. Thanks! |
|
@lengau do we have any updates on this. Can you please re-trigger the workflows for me. |
|
@bepri bringing this up again :) can you please retrigger the tests if everything looks good now |
bepri
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! I'm down to just nitpicks now, personally.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR introduces a shared Linter Service framework to craft-application, enabling downstream applications (Snapcraft, Charmcraft) to add and run linters consistently across pre- and post-build stages.
- Implements a service-based linter framework with class-level registration and central ignore configuration
- Adds a public
lintcommand with CLI support for ignore rules and custom ignore files - Includes example linters in Testcraft demonstrating pre-build and post-build linting
Reviewed Changes
Copilot reviewed 23 out of 23 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| craft_application/lint/_types.py | Defines core types (Stage, Severity, ExitCode, LintContext, LinterIssue, IgnoreSpec) and the should_ignore function |
| craft_application/lint/base.py | Implements AbstractLinter base class with validation for name and stage attributes |
| craft_application/lint/init.py | Public API exports for the lint module |
| craft_application/lint/linters/init.py | Placeholder namespace for built-in linters |
| craft_application/services/linter.py | Core LinterService implementation with registration, ignore config loading, and execution logic |
| craft_application/services/init.py | Exports LinterService |
| craft_application/services/service_factory.py | Adds linter and package service registrations with type hints |
| craft_application/commands/lint.py | Implements the lint CLI command with ignore rule parsing |
| craft_application/commands/init.py | Exports LintCommand |
| craft_application/util/logging.py | Updates print_error default from report_error to emit.error |
| testcraft/services/linter.py | Testcraft-specific linter implementations (MissingVersionLinter, EmptyArtifactLinter) and service subclass |
| testcraft/services/init.py | Registers TestcraftLinterService |
| testcraft/cli.py | Adds LintCommand to Testcraft CLI |
| tests/unit/services/test_linter_service.py | Unit tests for linter service registration, ignore rules, and hooks |
| tests/unit/commands/test_lint.py | Unit tests for lint command and CLI ignore config parsing |
| tests/integration/services/test_linter.py | Integration test for linter service with ignore config workflow |
| tests/spread/testcraft/lint/task.yaml | Spread test validating lint command output for pre and post stages |
| tests/spread/testcraft/lint/project-missing-version.yaml | Test fixture for pre-stage linting |
| tests/spread/testcraft/lint/project-with-version.yaml | Test fixture for post-stage linting |
| docs/reference/services/linter.rst | Reference documentation for the linter service API |
| docs/reference/services/index.rst | Adds linter service to index |
| docs/how-to-guides/add-a-linter.rst | How-to guide for adding new linters |
| docs/how-to-guides/index.rst | Adds linter guide to index |
Comments suppressed due to low confidence (1)
craft_application/util/logging.py:58
- The docstring mentions "
Emitter.report_error" but the code now usescraft_cli.emit.error(line 51). The docstring should be updated to match the actual implementation and referenceemit.errorinstead ofEmitter.report_error.
"""Handle a runtime error by transforming and printing it, then return the appropriate retcode.
The printing behavior can be customized by passing a different `print_error`, otherwise
`Emitter.report_error` is used.
"""
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…an API; improve tests and docs
45c057e to
3c97adf
Compare
|
@bepri could you please retrigger the tests |
|
Please refrain from force-pushes, it messes up GH's tracking of what files/changes we've already reviewed and thus slows down the review process. This PR is going to be squash-merged anyways, so the commit history doesn't matter :) Thanks! It looks like in the force push, a couple of my more recent suggestions you applied were dropped again: |
|
noted 🫡 @bepri I've reverted the missed changes and fixed the spread test failure to ensure the Testcraft linter service correctly extracts .testcraft tarballs even when no artifact directories are specified. This allows to fire TC100 again, and I've added a unit test to cover this behavior. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 23 out of 23 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
craft_application/services/linter.py:1
- Using
tarfile.extractall()without filters is unsafe and can lead to path traversal vulnerabilities. Usetar.extractall(path=tmp_path, filter='data')to prevent extraction of files outside the target directory.
# This file is part of craft-application.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 23 out of 23 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm approving this because I'm happy with the current functionality, but I have just a couple more minor nitpicks about the code that I'd like to be added before this is merged. Thanks!
@medubelko @lengau I think this is ready for your review.
medubelko
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @HamdaanAliQuatil, I'm a technical author at Canonical who looks at design and documentation.
I did some testing, and I have some questions about the UX and a suggestion for the CLI messages. Let me know what you think.
| if issues: | ||
| emit.message("lint results:") | ||
| for linter_name, linter_issues in linter.issues_by_linter.items(): | ||
| emit.message(f"{linter_name}:") | ||
| for issue in linter_issues: | ||
| location = f" ({issue.filename})" if issue.filename else "" | ||
| emit.message( | ||
| f" - [{issue.severity.name}] {issue.id}: {issue.message}{location}" | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From some initial testing, it seems that YAML syntax errors never reach this code. Instead, YamlError (I believe) interrupts. Is this expected?
Source:
name: testcraft-test
version: "1.0"
base: ubuntu@24.04
platforms:
platform-independent:
build-on: [amd64, arm64, ppc64el, s390x, riscv64]
build-for: [all]
parts:
my-test:
plugin: nil
parts:Result:
$ testcraft lint
error parsing '(unknown)': found duplicate key 'parts'
Detailed information:
while constructing a mapping
found duplicate key 'parts'
in "<unicode string>", line 1, column 1:
name: testcraft-test
^
Recommended resolution: Ensure (unknown) contains valid YAMLThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this is expected.
Invalid YAML is treated as a fatal parsing error, so the lint results block isn’t reached. This is expected for now, unless we decide to convert YAML errors into lint issues explicitly.
|
@lengau @bepri @medubelko updated the PR to incorporate your suggestions 🙌 |
make lint && make test?docs/reference/changelog.rst)?Closes #782
This PR introduces a shared Linter Service to craft-application so downstream apps (Snapcraft, Charmcraft) can add and run linters consistently.
The design of the Linter Service is as discussed in #782