diff --git a/README.md b/README.md index 751c87f1..baccc58e 100644 --- a/README.md +++ b/README.md @@ -183,6 +183,7 @@ The following rules are currently available: | Category | Title | Description | |-------------|-------------------------------------------------------------------------------------------------------|-----------------------------------------------------------| +| bugs | [annotation-without-metadata](https://docs.styra.com/regal/rules/bugs/annotation-without-metadata) | Annotation without metadata | | bugs | [constant-condition](https://docs.styra.com/regal/rules/bugs/constant-condition) | Constant condition | | bugs | [deprecated-builtin](https://docs.styra.com/regal/rules/bugs/deprecated-builtin) | Avoid using deprecated built-in functions | | bugs | [duplicate-rule](https://docs.styra.com/regal/rules/bugs/duplicate-rule) | Duplicate rule | diff --git a/bundle/regal/ast/comments.rego b/bundle/regal/ast/comments.rego index 3e0fe00c..0e481bd3 100644 --- a/bundle/regal/ast/comments.rego +++ b/bundle/regal/ast/comments.rego @@ -28,6 +28,11 @@ comments["metadata_attributes"] := { "custom", } +comments["annotation_match"](str) if regex.match( + `^(scope|title|description|related_resources|authors|organizations|schemas|entrypoint|custom)\s*:`, + str, +) + # METADATA # description: | # map of all ignore directive comments, like ("# regal ignore:line-length") diff --git a/bundle/regal/config/provided/data.yaml b/bundle/regal/config/provided/data.yaml index bde164a4..8a79c272 100644 --- a/bundle/regal/config/provided/data.yaml +++ b/bundle/regal/config/provided/data.yaml @@ -3,6 +3,8 @@ features: check-version: true rules: bugs: + annotation-without-metadata: + level: error constant-condition: level: error deprecated-builtin: diff --git a/bundle/regal/rules/bugs/annotation_without_metadata.rego b/bundle/regal/rules/bugs/annotation_without_metadata.rego new file mode 100644 index 00000000..a706b5c7 --- /dev/null +++ b/bundle/regal/rules/bugs/annotation_without_metadata.rego @@ -0,0 +1,17 @@ +# METADATA +# description: Annotation without metadata +package regal.rules.bugs["annotation-without-metadata"] + +import rego.v1 + +import data.regal.ast +import data.regal.result + +report contains violation if { + some block in ast.comments.blocks + + block[0].Location.col == 1 + ast.comments.annotation_match(trim_space(block[0].Text)) + + violation := result.fail(rego.metadata.chain(), result.location(block[0])) +} diff --git a/bundle/regal/rules/bugs/annotation_without_metadata_test.rego b/bundle/regal/rules/bugs/annotation_without_metadata_test.rego new file mode 100644 index 00000000..495025fc --- /dev/null +++ b/bundle/regal/rules/bugs/annotation_without_metadata_test.rego @@ -0,0 +1,60 @@ +package regal.rules.bugs["annotation-without-metadata_test"] + +import rego.v1 + +import data.regal.ast +import data.regal.config + +import data.regal.rules.bugs["annotation-without-metadata"] as rule + +test_fail_annotation_without_metadata if { + module := ast.with_rego_v1(` +# title: allow +allow := false + `) + + r := rule.report with input as module + r == {{ + "category": "bugs", + "description": "Annotation without metadata", + "level": "error", + "location": {"col": 1, "file": "policy.rego", "row": 6, "text": "# title: allow"}, + "related_resources": [{ + "description": "documentation", + "ref": config.docs.resolve_url("$baseUrl/$category/annotation-without-metadata", "bugs"), + }], + "title": "annotation-without-metadata", + }} +} + +test_success_annotation_with_metadata if { + module := ast.with_rego_v1(` +# METADATA +# title: allow +allow := false + `) + + r := rule.report with input as module + r == set() +} + +test_success_annotation_but_no_metadata_location if { + module := ast.with_rego_v1(` +allow := false # title: allow + `) + + r := rule.report with input as module + r == set() +} + +test_success_annotation_without_metadata_but_comment_preceeding if { + module := ast.with_rego_v1(` +# something that is not an annotation here will cancel this rule +# as this is less likely to be a mistake... but weird +# title: allow +allow := false + `) + + r := rule.report with input as module + r == set() +} diff --git a/docs/rules/bugs/annotation-without-metadata.md b/docs/rules/bugs/annotation-without-metadata.md new file mode 100644 index 00000000..4750e3df --- /dev/null +++ b/docs/rules/bugs/annotation-without-metadata.md @@ -0,0 +1,58 @@ +# annotation-without-metadata + +**Summary**: Annotation without metadata + +**Category**: Bugs + +**Avoid** +```rego +package policy + +import rego.v1 + +# description: allow allows +allow if { + # ... some conditions +} +``` + +**Prefer** +```rego +package policy + +import rego.v1 + +# METADATA +# description: allow allows +allow if { + # ... some conditions +} +``` + +## Rationale + +A comment that starts with `:` but is not part of a metadata block is likely a mistake. Add +`# METADATA` above the line to turn it into a +[metadata](https://www.openpolicyagent.org/docs/latest/policy-language/#annotations) block. + +## Configuration Options + +This linter rule provides the following configuration options: + +```yaml +rules: + bugs: + annotation-without-metadata: + # one of "error", "warning", "ignore" + level: error +``` + +## Related Resources + +- OPA Docs: [Annotations](https://www.openpolicyagent.org/docs/latest/policy-language/#annotations) + +## Community + +If you think you've found a problem with this rule or its documentation, would like to suggest improvements, new rules, +or just talk about Regal in general, please join us in the `#regal` channel in the Styra Community +[Slack](https://communityinviter.com/apps/styracommunity/signup)! diff --git a/e2e/testdata/violations/most_violations.rego b/e2e/testdata/violations/most_violations.rego index 44c8554f..cf3b6871 100644 --- a/e2e/testdata/violations/most_violations.rego +++ b/e2e/testdata/violations/most_violations.rego @@ -91,6 +91,9 @@ impossible_not if { not partial } +# title: annotation without metadata +some_rule := true + ### Idiomatic ### custom_has_key_construct(map, key) if {