Skip to content

Commit

Permalink
feat: error if single use annotations are used multiple times (#631)
Browse files Browse the repository at this point in the history
Closes partially #543

### Summary of Changes

Show an error if annotation that is not repeatable (default, unless it
has the `@Repeatable` annotation) is repeated.

---------

Co-authored-by: megalinter-bot <[email protected]>
  • Loading branch information
lars-reimann and megalinter-bot authored Oct 11, 2023
1 parent e8e2bf6 commit 17a5b7a
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 0 deletions.
8 changes: 8 additions & 0 deletions src/language/builtins/safe-ds-annotations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ export class SafeDsAnnotations extends SafeDsModuleMembers<SdsAnnotation> {
return this.getAnnotation(CORE_ANNOTATIONS_URI, 'Expert');
}

isRepeatable(node: SdsAnnotation | undefined): boolean {
return this.hasAnnotationCallOf(node, this.Repeatable);
}

private get Repeatable(): SdsAnnotation | undefined {
return this.getAnnotation(CORE_ANNOTATIONS_URI, 'Repeatable');
}

private hasAnnotationCallOf(node: SdsAnnotatedObject | undefined, expected: SdsAnnotation | undefined): boolean {
return annotationCallsOrEmpty(node).some((it) => {
const actual = it.annotation?.ref;
Expand Down
22 changes: 22 additions & 0 deletions src/language/validation/builtins/repeatable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ValidationAcceptor } from 'langium';
import { SdsAnnotatedObject } from '../../generated/ast.js';
import { SafeDsServices } from '../../safe-ds-module.js';
import { annotationCallsOrEmpty } from '../../helpers/nodeProperties.js';
import { duplicatesBy } from '../../helpers/collectionUtils.js';

export const CODE_ANNOTATION_NOT_REPEATABLE = 'annotation/not-repeatable';

export const singleUseAnnotationsMustNotBeRepeated =
(services: SafeDsServices) => (node: SdsAnnotatedObject, accept: ValidationAcceptor) => {
const callsOfSingleUseAnnotations = annotationCallsOrEmpty(node).filter((it) => {
const annotation = it.annotation?.ref;
return annotation && !services.builtins.Annotations.isRepeatable(annotation);
});

for (const duplicate of duplicatesBy(callsOfSingleUseAnnotations, (it) => it.annotation?.ref)) {
accept('error', `The annotation '${duplicate.annotation.$refText}' is not repeatable.`, {
node: duplicate,
code: CODE_ANNOTATION_NOT_REPEATABLE,
});
}
};
2 changes: 2 additions & 0 deletions src/language/validation/safe-ds-validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ import {
} from './other/declarations/annotationCalls.js';
import { memberAccessMustBeNullSafeIfReceiverIsNullable } from './other/expressions/memberAccesses.js';
import { importPackageMustExist, importPackageShouldNotBeEmpty } from './other/imports.js';
import { singleUseAnnotationsMustNotBeRepeated } from './builtins/repeatable.js';

/**
* Register custom validation checks.
Expand All @@ -98,6 +99,7 @@ export const registerValidationChecks = function (services: SafeDsServices) {
annotationCallAnnotationShouldNotBeExperimental(services),
annotationCallArgumentListShouldBeNeeded,
],
SdsAnnotatedObject: [singleUseAnnotationsMustNotBeRepeated(services)],
SdsArgument: [
argumentCorrespondingParameterShouldNotBeDeprecated(services),
argumentCorrespondingParameterShouldNotBeExperimental(services),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package tests.validation.builtins.annotations.repeatable

annotation SingleUse

@Repeatable
annotation MultiUse

// $TEST$ no error r"The annotation '\w*' is not repeatable\."
»@SingleUse«
// $TEST$ no error r"The annotation '\w*' is not repeatable\."
»@MultiUse«
// $TEST$ no error r"The annotation '\w*' is not repeatable\."
»@MultiUse«
// $TEST$ no error r"The annotation '\w*' is not repeatable\."
»@UnresolvedAnnotation«
// $TEST$ no error r"The annotation '\w*' is not repeatable\."
»@UnresolvedAnnotation«
class CorrectUse

// $TEST$ no error r"The annotation '\w*' is not repeatable\."
»@SingleUse«
// $TEST$ error "The annotation 'SingleUse' is not repeatable."
»@SingleUse«
class IncorrectUse
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ segment mySegment() -> (
// $TEST$ error "No value is assigned to this assignee."
»val k«, »val l« = unresolved();


// $TEST$ error "No value is assigned to this assignee."
»yield r1« = noResults();
// $TEST$ no error "No value is assigned to this assignee."
Expand Down

0 comments on commit 17a5b7a

Please sign in to comment.