From d95d95e495fdee8a2d333fe220801fc1f6aa0fd5 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Mon, 16 Oct 2023 15:41:23 +0200 Subject: [PATCH 1/5] fix: don't show unnecessary duplicate name errors when editing the stdlib --- src/language/validation/names.ts | 9 ++++++++- .../names/duplicates/across files/safeds.sdstest | 8 ++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 tests/resources/validation/names/duplicates/across files/safeds.sdstest diff --git a/src/language/validation/names.ts b/src/language/validation/names.ts index 6357ffb48..dc2bb7349 100644 --- a/src/language/validation/names.ts +++ b/src/language/validation/names.ts @@ -35,6 +35,7 @@ import { duplicatesBy } from '../helpers/collectionUtils.js'; import { isInPipelineFile, isInStubFile, isInTestFile } from '../helpers/fileExtensions.js'; import { declarationIsAllowedInPipelineFile, declarationIsAllowedInStubFile } from './other/modules.js'; import { SafeDsServices } from '../safe-ds-module.js'; +import { listBuiltinFiles } from '../builtins/fileFinder.js'; export const CODE_NAME_BLOCK_LAMBDA_PREFIX = 'name/block-lambda-prefix'; export const CODE_NAME_CASING = 'name/casing'; @@ -225,6 +226,7 @@ export const functionMustContainUniqueNames = (node: SdsFunction, accept: Valida export const moduleMemberMustHaveNameThatIsUniqueInPackage = (services: SafeDsServices) => { const packageManager = services.workspace.PackageManager; + const builtinUris = new Set(listBuiltinFiles().map((it) => it.toString())); return (node: SdsModule, accept: ValidationAcceptor): void => { for (const member of moduleMembersOrEmpty(node)) { @@ -233,7 +235,12 @@ export const moduleMemberMustHaveNameThatIsUniqueInPackage = (services: SafeDsSe const memberUri = getDocument(member).uri?.toString(); if ( - declarationsInPackage.some((it) => it.name === member.name && it.documentUri.toString() !== memberUri) + declarationsInPackage.some( + (it) => + it.name === member.name && + it.documentUri.toString() !== memberUri && + !builtinUris.has(it.documentUri.toString()), + ) ) { accept('error', `Multiple declarations in this package have the name '${member.name}'.`, { node: member, diff --git a/tests/resources/validation/names/duplicates/across files/safeds.sdstest b/tests/resources/validation/names/duplicates/across files/safeds.sdstest new file mode 100644 index 000000000..e7b6779cb --- /dev/null +++ b/tests/resources/validation/names/duplicates/across files/safeds.sdstest @@ -0,0 +1,8 @@ +package safeds.lang + +/* + * Declarations from builtin files should be excluded, so we don't get errors while editing them. + */ + +// $TEST$ no error r"Multiple declarations in this package have the name '\w*'\." + class »Any« From e0d3e8bc5644dd543c131195abfad11d62319b34 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Mon, 16 Oct 2023 15:44:47 +0200 Subject: [PATCH 2/5] fix: don't show duplicate error if single use annotation is used multiple times --- src/language/validation/builtins/repeatable.ts | 4 ++-- src/language/validation/safe-ds-validator.ts | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/language/validation/builtins/repeatable.ts b/src/language/validation/builtins/repeatable.ts index a4cf518bf..6f76a6aad 100644 --- a/src/language/validation/builtins/repeatable.ts +++ b/src/language/validation/builtins/repeatable.ts @@ -1,5 +1,5 @@ import { ValidationAcceptor } from 'langium'; -import { SdsAnnotatedObject } from '../../generated/ast.js'; +import { SdsDeclaration } from '../../generated/ast.js'; import { SafeDsServices } from '../../safe-ds-module.js'; import { annotationCallsOrEmpty } from '../../helpers/nodeProperties.js'; import { duplicatesBy } from '../../helpers/collectionUtils.js'; @@ -7,7 +7,7 @@ import { duplicatesBy } from '../../helpers/collectionUtils.js'; export const CODE_ANNOTATION_NOT_REPEATABLE = 'annotation/not-repeatable'; export const singleUseAnnotationsMustNotBeRepeated = - (services: SafeDsServices) => (node: SdsAnnotatedObject, accept: ValidationAcceptor) => { + (services: SafeDsServices) => (node: SdsDeclaration, accept: ValidationAcceptor) => { const callsOfSingleUseAnnotations = annotationCallsOrEmpty(node).filter((it) => { const annotation = it.annotation?.ref; return annotation && !services.builtins.Annotations.isRepeatable(annotation); diff --git a/src/language/validation/safe-ds-validator.ts b/src/language/validation/safe-ds-validator.ts index 93547e3f9..7e9e4bed3 100644 --- a/src/language/validation/safe-ds-validator.ts +++ b/src/language/validation/safe-ds-validator.ts @@ -122,7 +122,6 @@ export const registerValidationChecks = function (services: SafeDsServices) { annotationCallAnnotationShouldNotBeExperimental(services), annotationCallArgumentListShouldBeNeeded, ], - SdsAnnotatedObject: [singleUseAnnotationsMustNotBeRepeated(services)], SdsArgument: [ argumentCorrespondingParameterShouldNotBeDeprecated(services), argumentCorrespondingParameterShouldNotBeExperimental(services), @@ -145,7 +144,11 @@ export const registerValidationChecks = function (services: SafeDsServices) { ], SdsClassBody: [classBodyShouldNotBeEmpty], SdsConstraintList: [constraintListShouldNotBeEmpty], - SdsDeclaration: [nameMustNotStartWithBlockLambdaPrefix, nameShouldHaveCorrectCasing], + SdsDeclaration: [ + nameMustNotStartWithBlockLambdaPrefix, + nameShouldHaveCorrectCasing, + singleUseAnnotationsMustNotBeRepeated(services), + ], SdsEnum: [enumMustContainUniqueNames], SdsEnumBody: [enumBodyShouldNotBeEmpty], SdsEnumVariant: [enumVariantMustContainUniqueNames, enumVariantParameterListShouldNotBeEmpty], From 9af51409a47d9b61c00ad61706d8ee97ca78022d Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Mon, 16 Oct 2023 15:51:38 +0200 Subject: [PATCH 3/5] feat: info if Python name is identical to Safe-DS name --- src/language/builtins/safe-ds-annotations.ts | 74 +++++++++--- src/language/helpers/nodeProperties.ts | 22 ++++ .../toConstantExpressionOrUndefined.ts | 106 +++++++++--------- .../validation/builtins/pythonName.ts | 20 ++++ src/language/validation/safe-ds-validator.ts | 2 + .../annotations/pythonName/main.sdstest | 11 ++ .../pythonName/no annotation.sdstest | 4 + 7 files changed, 175 insertions(+), 64 deletions(-) create mode 100644 src/language/validation/builtins/pythonName.ts create mode 100644 tests/resources/validation/builtins/annotations/pythonName/main.sdstest create mode 100644 tests/resources/validation/builtins/annotations/pythonName/no annotation.sdstest diff --git a/src/language/builtins/safe-ds-annotations.ts b/src/language/builtins/safe-ds-annotations.ts index d0faedf0f..99da67e2d 100644 --- a/src/language/builtins/safe-ds-annotations.ts +++ b/src/language/builtins/safe-ds-annotations.ts @@ -1,16 +1,29 @@ -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 { + 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 { @@ -18,7 +31,7 @@ export class SafeDsAnnotations extends SafeDsModuleMembers { } isExperimental(node: SdsAnnotatedObject | undefined): boolean { - return this.hasAnnotationCallOf(node, this.Experimental); + return hasAnnotationCallOf(node, this.Experimental); } private get Experimental(): SdsAnnotation | undefined { @@ -26,29 +39,64 @@ export class SafeDsAnnotations extends SafeDsModuleMembers { } 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; + } + } + + private 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); + } } diff --git a/src/language/helpers/nodeProperties.ts b/src/language/helpers/nodeProperties.ts index af0eb35b9..a0340cfb3 100644 --- a/src/language/helpers/nodeProperties.ts +++ b/src/language/helpers/nodeProperties.ts @@ -17,6 +17,7 @@ import { SdsAbstractCall, SdsAbstractResult, SdsAnnotatedObject, + SdsAnnotation, SdsAnnotationCall, SdsArgument, SdsAssignee, @@ -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'; }; @@ -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 ?? []; }; diff --git a/src/language/partialEvaluation/toConstantExpressionOrUndefined.ts b/src/language/partialEvaluation/toConstantExpressionOrUndefined.ts index fbd55dc2f..f7e62fcbf 100644 --- a/src/language/partialEvaluation/toConstantExpressionOrUndefined.ts +++ b/src/language/partialEvaluation/toConstantExpressionOrUndefined.ts @@ -42,30 +42,34 @@ import { SdsTemplateString, } from '../generated/ast.js'; +/* c8 ignore start */ /** - * Tries to evaluate this expression. On success an SdsConstantExpression is returned, otherwise `null`. + * Tries to evaluate this expression. On success an SdsConstantExpression is returned, otherwise `undefined`. */ -export const toConstantExpressionOrUndefined = (node: AstNode): SdsConstantExpression | null => { +export const toConstantExpressionOrUndefined = (node: AstNode | undefined): SdsConstantExpression | undefined => { + if (!node) { + return undefined; + } + return toConstantExpressionOrNullImpl(node, new Map()); }; -/* c8 ignore start */ const toConstantExpressionOrNullImpl = ( node: AstNode, substitutions: ParameterSubstitutions, -): SdsConstantExpression | null => { +): SdsConstantExpression | undefined => { const simplifiedExpression = simplify(node, substitutions)?.unwrap(); if (simplifiedExpression instanceof SdsConstantExpression) { return simplifiedExpression; } else { - return null; + return undefined; } }; -const simplify = (node: AstNode, substitutions: ParameterSubstitutions): SdsSimplifiedExpression | null => { +const simplify = (node: AstNode, substitutions: ParameterSubstitutions): SdsSimplifiedExpression | undefined => { // Only expressions have a value if (!isSdsExpression(node)) { - return null; + return undefined; } // Base cases @@ -123,40 +127,40 @@ const simplify = (node: AstNode, substitutions: ParameterSubstitutions): SdsSimp const simplifyBlockLambda = ( _node: SdsBlockLambda, _substitutions: ParameterSubstitutions, -): SdsIntermediateBlockLambda | null => { +): SdsIntermediateBlockLambda | undefined => { // return when { // callableHasNoSideEffects(resultIfUnknown = true) -> SdsIntermediateBlockLambda( // parameters = parametersOrEmpty(), // results = blockLambdaResultsOrEmpty(), // substitutionsOnCreation = substitutions // ) - // else -> null + // else -> undefined // } - return null; + return undefined; }; const simplifyExpressionLambda = ( _node: SdsExpressionLambda, _substitutions: ParameterSubstitutions, -): SdsIntermediateExpressionLambda | null => { +): SdsIntermediateExpressionLambda | undefined => { // return when { // callableHasNoSideEffects(resultIfUnknown = true) -> SdsIntermediateExpressionLambda( // parameters = parametersOrEmpty(), // result = result, // substitutionsOnCreation = substitutions // ) - // else -> null + // else -> undefined // } - return null; + return undefined; }; const simplifyInfixOperation = ( _node: SdsInfixOperation, _substitutions: ParameterSubstitutions, -): SdsConstantExpression | null => { +): SdsConstantExpression | undefined => { // // By design none of the operators are short-circuited - // val constantLeft = leftOperand.toConstantExpressionOrUndefined(substitutions) ?: return null - // val constantRight = rightOperand.toConstantExpressionOrUndefined(substitutions) ?: return null + // val constantLeft = leftOperand.toConstantExpressionOrUndefined(substitutions) ?: return undefined + // val constantRight = rightOperand.toConstantExpressionOrUndefined(substitutions) ?: return undefined // // return when (operator()) { // Or -> simplifyLogicalOp(constantLeft, Boolean::or, constantRight) @@ -209,7 +213,7 @@ const simplifyInfixOperation = ( // ) // By -> { // if (constantRight == SdsConstantFloat(0.0) || constantRight == SdsConstantInt(0)) { - // return null + // return undefined // } // // simplifyArithmeticOp( @@ -224,7 +228,7 @@ const simplifyInfixOperation = ( // else -> constantLeft // } // } - return null; + return undefined; }; // private fun simplifyLogicalOp( @@ -237,7 +241,7 @@ const simplifyInfixOperation = ( // leftOperand is SdsConstantBoolean && rightOperand is SdsConstantBoolean -> { // SdsConstantBoolean(operation(leftOperand.value, rightOperand.value)) // } -// else -> null +// else -> undefined // } // } // @@ -255,7 +259,7 @@ const simplifyInfixOperation = ( // leftOperand is SdsConstantNumber && rightOperand is SdsConstantNumber -> { // SdsConstantBoolean(doubleOperation(leftOperand.value.toDouble(), rightOperand.value.toDouble())) // } -// else -> null +// else -> undefined // } // } // @@ -273,44 +277,44 @@ const simplifyInfixOperation = ( // leftOperand is SdsConstantNumber && rightOperand is SdsConstantNumber -> { // SdsConstantFloat(doubleOperation(leftOperand.value.toDouble(), rightOperand.value.toDouble())) // } -// else -> null +// else -> undefined // } // } const simplifyPrefixOperation = ( _node: SdsPrefixOperation, _substitutions: ParameterSubstitutions, -): SdsConstantExpression | null => { - // val constantOperand = operand.toConstantExpressionOrUndefined(substitutions) ?: return null +): SdsConstantExpression | undefined => { + // val constantOperand = operand.toConstantExpressionOrUndefined(substitutions) ?: return undefined // // return when (operator()) { // Not -> when (constantOperand) { // is SdsConstantBoolean -> SdsConstantBoolean(!constantOperand.value) - // else -> null + // else -> undefined // } // PrefixMinus -> when (constantOperand) { // is SdsConstantFloat -> SdsConstantFloat(-constantOperand.value) // is SdsConstantInt -> SdsConstantInt(-constantOperand.value) - // else -> null + // else -> undefined // } // } - return null; + return undefined; }; const simplifyTemplateString = ( node: SdsTemplateString, substitutions: ParameterSubstitutions, -): SdsConstantExpression | null => { +): SdsConstantExpression | undefined => { const constantExpressions = node.expressions.map((it) => toConstantExpressionOrNullImpl(it, substitutions)); - if (constantExpressions.some((it) => it === null)) { - return null; + if (constantExpressions.some((it) => it === undefined)) { + return undefined; } return new SdsConstantString(constantExpressions.map((it) => it!.toInterpolationString()).join('')); }; -const simplifyCall = (_node: SdsCall, _substitutions: ParameterSubstitutions): SdsSimplifiedExpression | null => { - // val simpleReceiver = simplifyReceiver(substitutions) ?: return null +const simplifyCall = (_node: SdsCall, _substitutions: ParameterSubstitutions): SdsSimplifiedExpression | undefined => { + // val simpleReceiver = simplifyReceiver(substitutions) ?: return undefined // val newSubstitutions = buildNewSubstitutions(simpleReceiver, substitutions) // // return when (simpleReceiver) { @@ -330,14 +334,14 @@ const simplifyCall = (_node: SdsCall, _substitutions: ParameterSubstitutions): S // ) // } // } - return null; + return undefined; }; // private fun SdsCall.simplifyReceiver(substitutions: ParameterSubstitutions): SdsIntermediateCallable? { // return when (val simpleReceiver = receiver.simplify(substitutions)) { // is SdsIntermediateRecord -> simpleReceiver.unwrap() as? SdsIntermediateCallable // is SdsIntermediateCallable -> simpleReceiver -// else -> return null +// else -> return undefined // } // } // @@ -356,7 +360,7 @@ const simplifyCall = (_node: SdsCall, _substitutions: ParameterSubstitutions): S // .groupBy { it.parameterOrNull() } // .mapValues { (parameter, arguments) -> // when { -// parameter == null -> null +// parameter == undefined -> undefined // parameter.isVariadic -> SdsIntermediateVariadicArguments( // arguments.map { it.simplify(oldSubstitutions) } // ) @@ -367,7 +371,7 @@ const simplifyCall = (_node: SdsCall, _substitutions: ParameterSubstitutions): S // return buildMap { // putAll(substitutionsOnCreation) // substitutionsOnCall.entries.forEach { (parameter, argument) -> -// if (parameter != null) { +// if (parameter != undefined) { // put(parameter, argument) // } // } @@ -377,19 +381,19 @@ const simplifyCall = (_node: SdsCall, _substitutions: ParameterSubstitutions): S const simplifyIndexedAccess = ( _node: SdsIndexedAccess, _substitutions: ParameterSubstitutions, -): SdsSimplifiedExpression | null => { - // val simpleReceiver = receiver.simplify(substitutions) as? SdsIntermediateVariadicArguments ?: return null - // val simpleIndex = index.simplify(substitutions) as? SdsConstantInt ?: return null +): SdsSimplifiedExpression | undefined => { + // val simpleReceiver = receiver.simplify(substitutions) as? SdsIntermediateVariadicArguments ?: return undefined + // val simpleIndex = index.simplify(substitutions) as? SdsConstantInt ?: return undefined // // return simpleReceiver.getArgumentByIndexOrNull(simpleIndex.value) // } - return null; + return undefined; }; const simplifyMemberAccess = ( _node: SdsMemberAccess, _substitutions: ParameterSubstitutions, -): SdsSimplifiedExpression | null => { +): SdsSimplifiedExpression | undefined => { // private fun SdsMemberAccess.simplifyMemberAccess(substitutions: ParameterSubstitutions): SdsSimplifiedExpression? { // if (member.declaration is SdsEnumVariant) { // return member.simplifyReference(substitutions) @@ -398,42 +402,42 @@ const simplifyMemberAccess = ( // return when (val simpleReceiver = receiver.simplify(substitutions)) { // SdsConstantNull -> when { // isNullSafe -> SdsConstantNull - // else -> null + // else -> undefined // } // is SdsIntermediateRecord -> simpleReceiver.getSubstitutionByReferenceOrNull(member) - // else -> null + // else -> undefined // } - return null; + return undefined; }; const simplifyReference = ( _node: SdsReference, _substitutions: ParameterSubstitutions, -): SdsSimplifiedExpression | null => { +): SdsSimplifiedExpression | undefined => { // return when (val declaration = this.declaration) { // is SdsEnumVariant -> when { // declaration.parametersOrEmpty().isEmpty() -> SdsConstantEnumVariant(declaration) - // else -> null + // else -> undefined // } // is SdsPlaceholder -> declaration.simplifyAssignee(substitutions) // is SdsParameter -> declaration.simplifyParameter(substitutions) // is SdsStep -> declaration.simplifyStep() - // else -> null + // else -> undefined // } - return null; + return undefined; }; // private fun SdsAbstractAssignee.simplifyAssignee(substitutions: ParameterSubstitutions): SdsSimplifiedExpression? { // val simpleFullAssignedExpression = closestAncestorOrNull() // ?.expression // ?.simplify(substitutions) -// ?: return null +// ?: return undefined // // return when (simpleFullAssignedExpression) { // is SdsIntermediateRecord -> simpleFullAssignedExpression.getSubstitutionByIndexOrNull(indexOrNull()) // else -> when { // indexOrNull() == 0 -> simpleFullAssignedExpression -// else -> null +// else -> undefined // } // } // } @@ -442,7 +446,7 @@ const simplifyReference = ( // return when { // this in substitutions -> substitutions[this] // isOptional() -> defaultValue?.simplify(substitutions) -// else -> null +// else -> undefined // } // } // @@ -452,7 +456,7 @@ const simplifyReference = ( // parameters = parametersOrEmpty(), // results = resultsOrEmpty() // ) -// else -> null +// else -> undefined // } // } /* c8 ignore stop */ diff --git a/src/language/validation/builtins/pythonName.ts b/src/language/validation/builtins/pythonName.ts new file mode 100644 index 000000000..1cb4865b0 --- /dev/null +++ b/src/language/validation/builtins/pythonName.ts @@ -0,0 +1,20 @@ +import { ValidationAcceptor } from 'langium'; +import { SdsDeclaration } from '../../generated/ast.js'; +import { SafeDsServices } from '../../safe-ds-module.js'; +import { findFirstAnnotationCallOf } from '../../helpers/nodeProperties.js'; + +export const CODE_PYTHON_NAME_SAME_AS_SAFE_DS_NAME = 'python-name/same-as-safe-ds-name'; + +export const pythonNameShouldDifferFromSafeDsName = + (services: SafeDsServices) => (node: SdsDeclaration, accept: ValidationAcceptor) => { + const pythonName = services.builtins.Annotations.getPythonName(node); + if (!pythonName || pythonName !== node.name) { + return; + } + + const annotationCall = findFirstAnnotationCallOf(node, services.builtins.Annotations.PythonName)!; + accept('info', 'The Python name is identical to the Safe-DS name, so the annotation call can be removed.', { + node: annotationCall, + code: CODE_PYTHON_NAME_SAME_AS_SAFE_DS_NAME, + }); + }; diff --git a/src/language/validation/safe-ds-validator.ts b/src/language/validation/safe-ds-validator.ts index 7e9e4bed3..6f94676fd 100644 --- a/src/language/validation/safe-ds-validator.ts +++ b/src/language/validation/safe-ds-validator.ts @@ -96,6 +96,7 @@ import { namedTypeTypeArgumentListMustNotHavePositionalArgumentsAfterNamedArguments, } from './other/types/namedTypes.js'; import { classMustNotInheritItself, classMustOnlyInheritASingleClass } from './inheritance.js'; +import {pythonNameShouldDifferFromSafeDsName} from "./builtins/pythonName.js"; /** * Register custom validation checks. @@ -147,6 +148,7 @@ export const registerValidationChecks = function (services: SafeDsServices) { SdsDeclaration: [ nameMustNotStartWithBlockLambdaPrefix, nameShouldHaveCorrectCasing, + pythonNameShouldDifferFromSafeDsName(services), singleUseAnnotationsMustNotBeRepeated(services), ], SdsEnum: [enumMustContainUniqueNames], diff --git a/tests/resources/validation/builtins/annotations/pythonName/main.sdstest b/tests/resources/validation/builtins/annotations/pythonName/main.sdstest new file mode 100644 index 000000000..a787c87d4 --- /dev/null +++ b/tests/resources/validation/builtins/annotations/pythonName/main.sdstest @@ -0,0 +1,11 @@ +package tests.validation.builtins.annotations.pythonName + +// $TEST$ info "The Python name is identical to the Safe-DS name, so the annotation call can be removed." +»@PythonName("TestClass1")« +class TestClass1 + +// $TEST$ no info "The Python name is identical to the Safe-DS name, so the annotation call can be removed." +»@PythonName("Test_Class_2")« +// $TEST$ no info "The Python name is identical to the Safe-DS name, so the annotation call can be removed." +»@PythonName("TestClass2")« +class TestClass2 diff --git a/tests/resources/validation/builtins/annotations/pythonName/no annotation.sdstest b/tests/resources/validation/builtins/annotations/pythonName/no annotation.sdstest new file mode 100644 index 000000000..400346c7b --- /dev/null +++ b/tests/resources/validation/builtins/annotations/pythonName/no annotation.sdstest @@ -0,0 +1,4 @@ +package tests.validation.builtins.annotations.pythonName + +// $TEST$ no info "The Python name is identical to the Safe-DS name, so the annotation call can be removed." +class TestClass3 From 415ce6a9b94bf189d0c99532c387628a157db788 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Mon, 16 Oct 2023 16:00:47 +0200 Subject: [PATCH 4/5] feat: info if Python module is identical to Safe-DS package --- src/language/builtins/safe-ds-annotations.ts | 2 +- .../validation/builtins/pythonModule.ts | 27 +++++++++++++++++++ .../validation/builtins/pythonName.ts | 11 +++++--- src/language/validation/safe-ds-validator.ts | 2 ++ .../annotations/pythonModule/error.sdstest | 4 +++ .../pythonModule/no annotation.sdstest | 3 +++ .../annotations/pythonModule/no error.sdstest | 6 +++++ 7 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 src/language/validation/builtins/pythonModule.ts create mode 100644 tests/resources/validation/builtins/annotations/pythonModule/error.sdstest create mode 100644 tests/resources/validation/builtins/annotations/pythonModule/no annotation.sdstest create mode 100644 tests/resources/validation/builtins/annotations/pythonModule/no error.sdstest diff --git a/src/language/builtins/safe-ds-annotations.ts b/src/language/builtins/safe-ds-annotations.ts index 99da67e2d..6a59ef51a 100644 --- a/src/language/builtins/safe-ds-annotations.ts +++ b/src/language/builtins/safe-ds-annotations.ts @@ -55,7 +55,7 @@ export class SafeDsAnnotations extends SafeDsModuleMembers { } } - private get PythonModule(): SdsAnnotation | undefined { + get PythonModule(): SdsAnnotation | undefined { return this.getAnnotation(CODE_GENERATION_URI, 'PythonModule'); } diff --git a/src/language/validation/builtins/pythonModule.ts b/src/language/validation/builtins/pythonModule.ts new file mode 100644 index 000000000..acd5b650b --- /dev/null +++ b/src/language/validation/builtins/pythonModule.ts @@ -0,0 +1,27 @@ +import { ValidationAcceptor } from 'langium'; +import { SdsModule } from '../../generated/ast.js'; +import { SafeDsServices } from '../../safe-ds-module.js'; +import { findFirstAnnotationCallOf } from '../../helpers/nodeProperties.js'; + +export const CODE_PYTHON_MODULE_SAME_AS_SAFE_DS_PACKAGE = 'python-module/same-as-safe-ds-package'; + +export const pythonModuleShouldDifferFromSafeDsPackage = (services: SafeDsServices) => { + const builtinAnnotations = services.builtins.Annotations; + + return (node: SdsModule, accept: ValidationAcceptor) => { + const pythonModule = builtinAnnotations.getPythonModule(node); + if (!pythonModule || pythonModule !== node.name) { + return; + } + + const annotationCall = findFirstAnnotationCallOf(node, builtinAnnotations.PythonModule)!; + accept( + 'info', + 'The Python module is identical to the Safe-DS package, so the annotation call can be removed.', + { + node: annotationCall, + code: CODE_PYTHON_MODULE_SAME_AS_SAFE_DS_PACKAGE, + }, + ); + }; +}; diff --git a/src/language/validation/builtins/pythonName.ts b/src/language/validation/builtins/pythonName.ts index 1cb4865b0..26a8235ef 100644 --- a/src/language/validation/builtins/pythonName.ts +++ b/src/language/validation/builtins/pythonName.ts @@ -5,16 +5,19 @@ import { findFirstAnnotationCallOf } from '../../helpers/nodeProperties.js'; export const CODE_PYTHON_NAME_SAME_AS_SAFE_DS_NAME = 'python-name/same-as-safe-ds-name'; -export const pythonNameShouldDifferFromSafeDsName = - (services: SafeDsServices) => (node: SdsDeclaration, accept: ValidationAcceptor) => { - const pythonName = services.builtins.Annotations.getPythonName(node); +export const pythonNameShouldDifferFromSafeDsName = (services: SafeDsServices) => { + const builtinAnnotations = services.builtins.Annotations; + + return (node: SdsDeclaration, accept: ValidationAcceptor) => { + const pythonName = builtinAnnotations.getPythonName(node); if (!pythonName || pythonName !== node.name) { return; } - const annotationCall = findFirstAnnotationCallOf(node, services.builtins.Annotations.PythonName)!; + const annotationCall = findFirstAnnotationCallOf(node, builtinAnnotations.PythonName)!; accept('info', 'The Python name is identical to the Safe-DS name, so the annotation call can be removed.', { node: annotationCall, code: CODE_PYTHON_NAME_SAME_AS_SAFE_DS_NAME, }); }; +}; diff --git a/src/language/validation/safe-ds-validator.ts b/src/language/validation/safe-ds-validator.ts index 6f94676fd..e06f1907e 100644 --- a/src/language/validation/safe-ds-validator.ts +++ b/src/language/validation/safe-ds-validator.ts @@ -97,6 +97,7 @@ import { } from './other/types/namedTypes.js'; import { classMustNotInheritItself, classMustOnlyInheritASingleClass } from './inheritance.js'; import {pythonNameShouldDifferFromSafeDsName} from "./builtins/pythonName.js"; +import {pythonModuleShouldDifferFromSafeDsPackage} from "./builtins/pythonModule.js"; /** * Register custom validation checks. @@ -169,6 +170,7 @@ export const registerValidationChecks = function (services: SafeDsServices) { moduleMemberMustHaveNameThatIsUniqueInPackage(services), moduleMustContainUniqueNames, moduleWithDeclarationsMustStatePackage, + pythonModuleShouldDifferFromSafeDsPackage(services), ], SdsNamedType: [ namedTypeDeclarationShouldNotBeDeprecated(services), diff --git a/tests/resources/validation/builtins/annotations/pythonModule/error.sdstest b/tests/resources/validation/builtins/annotations/pythonModule/error.sdstest new file mode 100644 index 000000000..34fd5c659 --- /dev/null +++ b/tests/resources/validation/builtins/annotations/pythonModule/error.sdstest @@ -0,0 +1,4 @@ +// $TEST$ info "The Python module is identical to the Safe-DS package, so the annotation call can be removed." +»@PythonModule("tests.validation.builtins.annotations.pythonModule")« + +package tests.validation.builtins.annotations.pythonModule diff --git a/tests/resources/validation/builtins/annotations/pythonModule/no annotation.sdstest b/tests/resources/validation/builtins/annotations/pythonModule/no annotation.sdstest new file mode 100644 index 000000000..057e3d851 --- /dev/null +++ b/tests/resources/validation/builtins/annotations/pythonModule/no annotation.sdstest @@ -0,0 +1,3 @@ +// $TEST$ no info "The Python module is identical to the Safe-DS package, so the annotation call can be removed." + +package tests.validation.builtins.annotations.pythonModule diff --git a/tests/resources/validation/builtins/annotations/pythonModule/no error.sdstest b/tests/resources/validation/builtins/annotations/pythonModule/no error.sdstest new file mode 100644 index 000000000..41bf3d998 --- /dev/null +++ b/tests/resources/validation/builtins/annotations/pythonModule/no error.sdstest @@ -0,0 +1,6 @@ +// $TEST$ no info "The Python module is identical to the Safe-DS package, so the annotation call can be removed." +»@PythonModule("tests.validation.builtins.annotations.pythonModule.other")« +// $TEST$ no info "The Python module is identical to the Safe-DS package, so the annotation call can be removed." +»@PythonModule("tests.validation.builtins.annotations.pythonModule")« + +package tests.validation.builtins.annotations.pythonModule From ff29f0014d49a6f822558664464647077076e42e Mon Sep 17 00:00:00 2001 From: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> Date: Mon, 16 Oct 2023 14:03:47 +0000 Subject: [PATCH 5/5] style: apply automated linter fixes --- src/language/validation/safe-ds-validator.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/language/validation/safe-ds-validator.ts b/src/language/validation/safe-ds-validator.ts index e06f1907e..e052a8e74 100644 --- a/src/language/validation/safe-ds-validator.ts +++ b/src/language/validation/safe-ds-validator.ts @@ -96,8 +96,8 @@ import { namedTypeTypeArgumentListMustNotHavePositionalArgumentsAfterNamedArguments, } from './other/types/namedTypes.js'; import { classMustNotInheritItself, classMustOnlyInheritASingleClass } from './inheritance.js'; -import {pythonNameShouldDifferFromSafeDsName} from "./builtins/pythonName.js"; -import {pythonModuleShouldDifferFromSafeDsPackage} from "./builtins/pythonModule.js"; +import { pythonNameShouldDifferFromSafeDsName } from './builtins/pythonName.js'; +import { pythonModuleShouldDifferFromSafeDsPackage } from './builtins/pythonModule.js'; /** * Register custom validation checks.