Skip to content

Conversation

@saschagrunert
Copy link
Member

@saschagrunert saschagrunert commented Oct 30, 2025

Add a new linter that allows projects to enforce using preferred markers instead of equivalent but different marker identifiers.

  • Marker preferences are configurable
  • Auto-fix support
  • Maintains marker expressions (e.g., :=value, :key=value) during replacement
  • When multiple equivalent markers exist on the same field/type, consolidates them into a single preferred marker
  • Handles markers inherited from type aliases
  • Sorted processing ensures consistent, reproducible results

The linter is disabled by default and requires explicit configuration to enable.

Fixes #139

@k8s-ci-robot k8s-ci-robot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Oct 30, 2025
@saschagrunert saschagrunert marked this pull request as ready for review October 30, 2025 08:58
@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: saschagrunert
Once this PR has been reviewed and has the lgtm label, please assign jpbetz for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@k8s-ci-robot k8s-ci-robot added the cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. label Oct 30, 2025
@k8s-ci-robot k8s-ci-robot requested a review from sivchari October 30, 2025 08:58
@k8s-ci-robot k8s-ci-robot added the size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. label Oct 30, 2025
@saschagrunert saschagrunert force-pushed the add-preferredmarkers-linter branch 6 times, most recently from 8b3ad22 to c13e1ee Compare October 30, 2025 09:43
Introduce a new configurable linter that enforces consistent marker usage
across codebases by ensuring preferred marker identifiers are used instead
of equivalent alternatives.

Key Features:
- Configurable marker preferences with multiple equivalent identifiers
- Auto-fix support with expression preservation
- Smart duplicate handling (consolidates multiple equivalents)
- Type-aware checking (handles markers inherited from type aliases)
- Deterministic output with sorted processing
- Comprehensive configuration validation

Signed-off-by: Sascha Grunert <sgrunert@redhat.com>
@saschagrunert saschagrunert force-pushed the add-preferredmarkers-linter branch from c13e1ee to c004f0d Compare October 30, 2025 09:44
@saschagrunert saschagrunert changed the title WIP: Add preferredmarkers linter Add preferredmarkers linter Oct 30, 2025
@k8s-ci-robot k8s-ci-robot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Oct 30, 2025
Comment on lines +580 to +581
- `+k8s:optional`
- `+kubebuilder:validation:Optional`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- `+k8s:optional`
- `+kubebuilder:validation:Optional`
- `+optional`
- `+k8s:optional`
- `+kubebuilder:validation:Optional`

markers:
- preferredIdentifier: "custom:preferred"
equivalentIdentifiers:
- "custom:old:marker"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I had a value for the marker, how would the value be translated? For example, if we switch from +kubebuilder:validation:MaxLength:=3 to +k8s:maxLength:=3, is that handled?

What about something more complex like XValidation that has multiple arguments?


Reading further on, these are preserved verbatim.

If in the future we wanted to remap, is the current config extensible in any way? Possibly not.

I'm expecting a future where (and this is somewhat OpenShift specific), we want to remap

+openshift:validation:featureGate=ABC --> +k8s:ifEnabled(FeatureGate=ABC)

Perhaps we want to make the equivalentIdentifiers a list of objects so that we can expand with a mapping option later?

OptionalFieldKubebuilder string `json:"optionalFieldKubebuilder"` // want `field OptionalFieldKubebuilder uses marker "kubebuilder:validation:Optional", should use preferred marker "k8s:optional" instead`

// Inherited from type alias
OptionalFieldKubebuilderTypeAlias OptionalTypeKubebuilder `json:"optionalFieldKubebuilderTypeAlias"` // want `field OptionalFieldKubebuilderTypeAlias uses marker "kubebuilder:validation:Optional", should use preferred marker "k8s:optional" instead`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given this will be reported at the type level, is it beneficial for it to be reported here again?

Expect(errs).To(HaveLen(0), "No errors were expected")
}
},
Entry("With a valid preferredmarkers configuration", testCase{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have a case that checks the preferred identifier is not in the list of equivalent identifiers? So we wouldn't create a loop of trying to replace an identifier with itself

// Config is the configuration type
// for the preferredmarkers linter.
type Config struct {
// Markers is the unique set of preferred markers
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We follow the conventions of lowercase first word on the godoc on these config types

Suggested change
// Markers is the unique set of preferred markers
// markers is the unique set of preferred markers

return
}

markerSet := utils.TypeAwareMarkerCollectionForField(pass, markersAccess, field)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect this is going to cause confusion since we are inspecting the types separately


// Check for unnamed expression (simple case like "+marker:=value")
if unnamedValue, ok := expressions[markers.UnnamedExpression]; ok {
return ":=" + unnamedValue
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think := is universally compatible, just use =.

Kubebuilder supports :=, but to my knowledge, the +k8s markers do not

// preferred identifier while preserving any expressions from the original
// marker.
func buildReplacementMarker(marker markers.Marker, preferredIdentifier string) string {
return "// +" + preferredIdentifier + formatExpressions(marker.Expressions)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@everettraven can you PTAL at this given your context with the DV parsing, is this likely to break/change?


// Type at end of file without trailing newline
// +kubebuilder:validation:Optional
type EndOfFileType string // want `type EndOfFileType uses marker "kubebuilder:validation:Optional", should use preferred marker "k8s:optional" instead` No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this important to test? The change isn't actually at the end of the file, it's on line 4?

How did you come across this issue to want to test EOF?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

New Linter: Preferred Markers

3 participants