Skip to content

Commit

Permalink
feat: check @PythonName and @PythonModule (#641)
Browse files Browse the repository at this point in the history
Closes partially #543

### Summary of Changes

Show an info if
* `@PythonName` is identical to the Safe-DS name of a declaration,
* `@PythonModule` is identical to the Safe-DS package.

In those cases, the annotations could be removed.

---------

Co-authored-by: megalinter-bot <[email protected]>
  • Loading branch information
lars-reimann and megalinter-bot authored Oct 16, 2023
1 parent 38d1181 commit 5a9dcbb
Show file tree
Hide file tree
Showing 14 changed files with 243 additions and 69 deletions.
74 changes: 61 additions & 13 deletions src/language/builtins/safe-ds-annotations.ts
Original file line number Diff line number Diff line change
@@ -1,54 +1,102 @@
import { isSdsAnnotation, SdsAnnotatedObject, SdsAnnotation, SdsParameter } from '../generated/ast.js';
import { annotationCallsOrEmpty } from '../helpers/nodeProperties.js';
import { isSdsAnnotation, SdsAnnotatedObject, SdsAnnotation, SdsModule, SdsParameter } from '../generated/ast.js';
import { argumentsOrEmpty, findFirstAnnotationCallOf, hasAnnotationCallOf } from '../helpers/nodeProperties.js';
import { SafeDsModuleMembers } from './safe-ds-module-members.js';
import { resourceNameToUri } from '../../helpers/resources.js';
import { URI } from 'langium';
import { SafeDsServices } from '../safe-ds-module.js';
import { SafeDsNodeMapper } from '../helpers/safe-ds-node-mapper.js';
import { toConstantExpressionOrUndefined } from '../partialEvaluation/toConstantExpressionOrUndefined.js';
import { SdsConstantExpression, SdsConstantString } from '../partialEvaluation/model.js';

const ANNOTATION_USAGE_URI = resourceNameToUri('builtins/safeds/lang/annotationUsage.sdsstub');
const CODE_GENERATION_URI = resourceNameToUri('builtins/safeds/lang/codeGeneration.sdsstub');
const IDE_INTEGRATION_URI = resourceNameToUri('builtins/safeds/lang/ideIntegration.sdsstub');
const MATURITY_URI = resourceNameToUri('builtins/safeds/lang/maturity.sdsstub');

export class SafeDsAnnotations extends SafeDsModuleMembers<SdsAnnotation> {
private readonly nodeMapper: SafeDsNodeMapper;

constructor(services: SafeDsServices) {
super(services);

this.nodeMapper = services.helpers.NodeMapper;
}

isDeprecated(node: SdsAnnotatedObject | undefined): boolean {
return this.hasAnnotationCallOf(node, this.Deprecated);
return hasAnnotationCallOf(node, this.Deprecated);
}

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

isExperimental(node: SdsAnnotatedObject | undefined): boolean {
return this.hasAnnotationCallOf(node, this.Experimental);
return hasAnnotationCallOf(node, this.Experimental);
}

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

isExpert(node: SdsParameter | undefined): boolean {
return this.hasAnnotationCallOf(node, this.Expert);
return hasAnnotationCallOf(node, this.Expert);
}

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

getPythonModule(node: SdsModule | undefined): string | undefined {
const value = this.getArgumentValue(node, this.PythonModule, 'qualifiedName');
if (value instanceof SdsConstantString) {
return value.value;
} else {
return undefined;
}
}

get PythonModule(): SdsAnnotation | undefined {
return this.getAnnotation(CODE_GENERATION_URI, 'PythonModule');
}

getPythonName(node: SdsAnnotatedObject | undefined): string | undefined {
const value = this.getArgumentValue(node, this.PythonName, 'name');
if (value instanceof SdsConstantString) {
return value.value;
} else {
return undefined;
}
}

get PythonName(): SdsAnnotation | undefined {
return this.getAnnotation(CODE_GENERATION_URI, 'PythonName');
}

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

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

private hasAnnotationCallOf(node: SdsAnnotatedObject | undefined, expected: SdsAnnotation | undefined): boolean {
return annotationCallsOrEmpty(node).some((it) => {
const actual = it.annotation?.ref;
return actual === expected;
});
}

private getAnnotation(uri: URI, name: string): SdsAnnotation | undefined {
return this.getModuleMember(uri, name, isSdsAnnotation);
}

/**
* Finds the first call of the given annotation on the given node and returns the value that is assigned to the
* parameter with the given name.
*/
private getArgumentValue(
node: SdsAnnotatedObject | undefined,
annotation: SdsAnnotation | undefined,
parameterName: string,
): SdsConstantExpression | undefined {
const annotationCall = findFirstAnnotationCallOf(node, annotation);
const expression = argumentsOrEmpty(annotationCall).find(
(it) => this.nodeMapper.argumentToParameterOrUndefined(it)?.name === parameterName,
)?.value;
return toConstantExpressionOrUndefined(expression);
}
}
22 changes: 22 additions & 0 deletions src/language/helpers/nodeProperties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
SdsAbstractCall,
SdsAbstractResult,
SdsAnnotatedObject,
SdsAnnotation,
SdsAnnotationCall,
SdsArgument,
SdsAssignee,
Expand Down Expand Up @@ -57,6 +58,16 @@ import { AstNode, getContainerOfType, stream } from 'langium';
// Checks
// -------------------------------------------------------------------------------------------------

export const hasAnnotationCallOf = (
node: SdsAnnotatedObject | undefined,
expected: SdsAnnotation | undefined,
): boolean => {
return annotationCallsOrEmpty(node).some((it) => {
const actual = it.annotation?.ref;
return actual === expected;
});
};

export const isInternal = (node: SdsDeclaration): boolean => {
return isSdsSegment(node) && node.visibility === 'internal';
};
Expand Down Expand Up @@ -121,6 +132,17 @@ export const annotationCallsOrEmpty = (node: SdsAnnotatedObject | undefined): Sd
return node?.annotationCalls ?? [];
}
};

export const findFirstAnnotationCallOf = (
node: SdsAnnotatedObject | undefined,
expected: SdsAnnotation | undefined,
): SdsAnnotationCall | undefined => {
return annotationCallsOrEmpty(node).find((it) => {
const actual = it.annotation?.ref;
return actual === expected;
});
};

export const argumentsOrEmpty = (node: SdsAbstractCall | undefined): SdsArgument[] => {
return node?.argumentList?.arguments ?? [];
};
Expand Down
Loading

0 comments on commit 5a9dcbb

Please sign in to comment.