diff --git a/src/language/builtins/safe-ds-core-classes.ts b/src/language/builtins/safe-ds-core-classes.ts index ea12a5601..51cc491cb 100644 --- a/src/language/builtins/safe-ds-core-classes.ts +++ b/src/language/builtins/safe-ds-core-classes.ts @@ -2,7 +2,7 @@ import { SafeDsServices } from '../safe-ds-module.js'; import { resolveRelativePathToBuiltinFile } from './fileFinder.js'; import { isSdsClass, isSdsModule, SdsClass } from '../generated/ast.js'; import { LangiumDocuments } from 'langium'; -import { moduleMembersOrEmpty } from '../helpers/shortcuts.js'; +import { moduleMembersOrEmpty } from '../helpers/nodeProperties.js'; const CORE_CLASSES_URI = resolveRelativePathToBuiltinFile('safeds/lang/coreClasses.sdsstub'); @@ -23,9 +23,10 @@ export class SafeDsCoreClasses { return this.cachedAny; } - private cachedBoolean: SdsClass | undefined; /* c8 ignore stop */ + private cachedBoolean: SdsClass | undefined; + get Boolean(): SdsClass | undefined { if (!this.cachedBoolean) { this.cachedBoolean = this.getClass('Boolean'); diff --git a/src/language/formatting/safe-ds-formatter.ts b/src/language/formatting/safe-ds-formatter.ts index 77e60c6c3..f9c58c5c9 100644 --- a/src/language/formatting/safe-ds-formatter.ts +++ b/src/language/formatting/safe-ds-formatter.ts @@ -9,12 +9,12 @@ import { isAstNode, } from 'langium'; import * as ast from '../generated/ast.js'; -import { annotationCallsOrEmpty, literalsOrEmpty, typeArgumentsOrEmpty } from '../helpers/shortcuts.js'; import noSpace = Formatting.noSpace; import newLine = Formatting.newLine; import newLines = Formatting.newLines; import oneSpace = Formatting.oneSpace; import indent = Formatting.indent; +import { annotationCallsOrEmpty, literalsOrEmpty, typeArgumentsOrEmpty } from '../helpers/nodeProperties.js'; const newLinesWithIndent = function (count: number, options?: FormattingActionOptions): FormattingAction { return { diff --git a/src/language/helpers/ast.ts b/src/language/helpers/astUtils.ts similarity index 100% rename from src/language/helpers/ast.ts rename to src/language/helpers/astUtils.ts diff --git a/src/language/helpers/checks.ts b/src/language/helpers/checks.ts deleted file mode 100644 index 884e27e94..000000000 --- a/src/language/helpers/checks.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { - isSdsAttribute, - isSdsClass, - isSdsEnum, - isSdsFunction, - isSdsSegment, - SdsArgument, - SdsClassMember, - SdsDeclaration, - SdsTypeArgument, -} from '../generated/ast.js'; - -export const isInternal = (node: SdsDeclaration): boolean => { - return isSdsSegment(node) && node.visibility === 'internal'; -}; - -export const isNamedArgument = (node: SdsArgument): boolean => { - return Boolean(node.parameter); -}; - -export const isNamedTypeArgument = (node: SdsTypeArgument): boolean => { - return Boolean(node.typeParameter); -}; - -export const isStatic = (node: SdsClassMember): boolean => { - if (isSdsClass(node) || isSdsEnum(node)) { - return true; - } else if (isSdsAttribute(node)) { - return node.isStatic; - } else if (isSdsFunction(node)) { - return node.isStatic; - } else { - /* c8 ignore next 2 */ - return false; - } -}; diff --git a/src/language/helpers/collectionUtils.ts b/src/language/helpers/collectionUtils.ts new file mode 100644 index 000000000..653e6ec17 --- /dev/null +++ b/src/language/helpers/collectionUtils.ts @@ -0,0 +1,34 @@ +/** + * Returns the unique element in the array, or `undefined` if none or multiple exist. + */ +export const uniqueOrUndefined = (elements: T[]): T | undefined => { + if (elements.length === 1) { + return elements[0]; + } + + return undefined; +}; + +/** + * Returns the elements of the array that are labeled the same as a previous element. The first element with a label is + * not included. Neither are elements with an undefined label. + */ +export const duplicatesBy = function* ( + elements: Iterable, + labeler: (element: T) => K | undefined, +): Generator { + const knownLabels = new Set(); + + for (const element of elements) { + const label = labeler(element); + if (label === undefined) { + continue; + } + + if (knownLabels.has(label)) { + yield element; + } else { + knownLabels.add(label); + } + } +}; diff --git a/src/language/helpers/idManager.ts b/src/language/helpers/idManager.ts new file mode 100644 index 000000000..c9ebd36a5 --- /dev/null +++ b/src/language/helpers/idManager.ts @@ -0,0 +1,34 @@ +/** + * Handles the mapping of objects, usually nodes of an Safe-DS AST, to their IDs. + */ +export class IdManager { + /** + * Maps an object to an ID. + */ + private objToId: WeakMap = new WeakMap(); + + /** + * The next available ID. + */ + private nextId = 0; + + /** + * Assigns the next available ID to the given object unless it already has one and returns the ID for this object. + */ + assignId(obj: T): Id { + if (!this.objToId.has(obj)) { + this.objToId.set(obj, this.nextId++); + } + return this.objToId.get(obj)!; + } + + /** + * Removes all mappings between object and ID and resets the counter. + */ + reset() { + this.objToId = new WeakMap(); + this.nextId = 0; + } +} + +export type Id = number; diff --git a/src/language/helpers/nodeProperties.ts b/src/language/helpers/nodeProperties.ts new file mode 100644 index 000000000..3490fa941 --- /dev/null +++ b/src/language/helpers/nodeProperties.ts @@ -0,0 +1,179 @@ +import { + isSdsAssignment, + isSdsAttribute, + isSdsBlockLambdaResult, + isSdsClass, + isSdsDeclaration, + isSdsEnum, + isSdsEnumVariant, + isSdsFunction, + isSdsModule, + isSdsModuleMember, + isSdsPlaceholder, + isSdsSegment, + isSdsTypeParameterList, + SdsAbstractCall, + SdsAnnotatedObject, + SdsAnnotationCall, + SdsArgument, + SdsAssignee, + SdsAssignment, + SdsBlock, + SdsBlockLambda, + SdsBlockLambdaResult, + SdsCallable, + SdsClass, + SdsClassMember, + SdsDeclaration, + SdsEnum, + SdsEnumVariant, + SdsImport, + SdsImportedDeclaration, + SdsLiteral, + SdsLiteralType, + SdsModule, + SdsModuleMember, + SdsNamedTypeDeclaration, + SdsParameter, + SdsPlaceholder, + SdsQualifiedImport, + SdsResult, + SdsResultList, + SdsStatement, + SdsTypeArgument, + SdsTypeArgumentList, + SdsTypeParameter, + SdsTypeParameterList, +} from '../generated/ast.js'; +import { AstNode, getContainerOfType, stream } from 'langium'; + +// ------------------------------------------------------------------------------------------------- +// Checks +// ------------------------------------------------------------------------------------------------- + +export const isInternal = (node: SdsDeclaration): boolean => { + return isSdsSegment(node) && node.visibility === 'internal'; +}; + +export const isNamedArgument = (node: SdsArgument): boolean => { + return Boolean(node.parameter); +}; + +export const isNamedTypeArgument = (node: SdsTypeArgument): boolean => { + return Boolean(node.typeParameter); +}; + +export const isStatic = (node: SdsClassMember): boolean => { + if (isSdsClass(node) || isSdsEnum(node)) { + return true; + } else if (isSdsAttribute(node)) { + return node.isStatic; + } else if (isSdsFunction(node)) { + return node.isStatic; + } else { + /* c8 ignore next 2 */ + return false; + } +}; + +// ------------------------------------------------------------------------------------------------- +// Accessors for list elements +// ------------------------------------------------------------------------------------------------- + +export const annotationCallsOrEmpty = (node: SdsAnnotatedObject | undefined): SdsAnnotationCall[] => { + if (!node) { + /* c8 ignore next 2 */ + return []; + } + + if (isSdsDeclaration(node)) { + return node?.annotationCallList?.annotationCalls ?? node?.annotationCalls ?? []; + } else { + /* c8 ignore next 2 */ + return node?.annotationCalls ?? []; + } +}; +export const argumentsOrEmpty = (node: SdsAbstractCall | undefined): SdsArgument[] => { + return node?.argumentList?.arguments ?? []; +}; +export const assigneesOrEmpty = (node: SdsAssignment | undefined): SdsAssignee[] => { + return node?.assigneeList?.assignees ?? []; +}; +export const blockLambdaResultsOrEmpty = (node: SdsBlockLambda | undefined): SdsBlockLambdaResult[] => { + return stream(statementsOrEmpty(node?.body)) + .filter(isSdsAssignment) + .flatMap(assigneesOrEmpty) + .filter(isSdsBlockLambdaResult) + .toArray(); +}; +export const importedDeclarationsOrEmpty = (node: SdsQualifiedImport | undefined): SdsImportedDeclaration[] => { + return node?.importedDeclarationList?.importedDeclarations ?? []; +}; + +export const literalsOrEmpty = (node: SdsLiteralType | undefined): SdsLiteral[] => { + return node?.literalList?.literals ?? []; +}; +export const classMembersOrEmpty = ( + node: SdsClass | undefined, + filterFunction: (member: SdsClassMember) => boolean = () => true, +): SdsClassMember[] => { + return node?.body?.members?.filter(filterFunction) ?? []; +}; + +export const enumVariantsOrEmpty = (node: SdsEnum | undefined): SdsEnumVariant[] => { + return node?.body?.variants ?? []; +}; + +export const importsOrEmpty = (node: SdsModule | undefined): SdsImport[] => { + return node?.imports ?? []; +}; + +export const moduleMembersOrEmpty = (node: SdsModule | undefined): SdsModuleMember[] => { + return node?.members?.filter(isSdsModuleMember) ?? []; +}; + +export const packageNameOrUndefined = (node: AstNode | undefined): string | undefined => { + return getContainerOfType(node, isSdsModule)?.name; +}; + +export const parametersOrEmpty = (node: SdsCallable | undefined): SdsParameter[] => { + return node?.parameterList?.parameters ?? []; +}; + +export const placeholdersOrEmpty = (node: SdsBlock | undefined): SdsPlaceholder[] => { + return stream(statementsOrEmpty(node)) + .filter(isSdsAssignment) + .flatMap(assigneesOrEmpty) + .filter(isSdsPlaceholder) + .toArray(); +}; + +export const resultsOrEmpty = (node: SdsResultList | undefined): SdsResult[] => { + return node?.results ?? []; +}; + +export const statementsOrEmpty = (node: SdsBlock | undefined): SdsStatement[] => { + return node?.statements ?? []; +}; + +export const typeArgumentsOrEmpty = (node: SdsTypeArgumentList | undefined): SdsTypeArgument[] => { + return node?.typeArguments ?? []; +}; + +export const typeParametersOrEmpty = ( + node: SdsTypeParameterList | SdsNamedTypeDeclaration | undefined, +): SdsTypeParameter[] => { + if (!node) { + return []; + } + + if (isSdsTypeParameterList(node)) { + return node.typeParameters; + } else if (isSdsClass(node)) { + return typeParametersOrEmpty(node.typeParameterList); + } else if (isSdsEnumVariant(node)) { + return typeParametersOrEmpty(node.typeParameterList); + } /* c8 ignore start */ else { + return []; + } /* c8 ignore stop */ +}; diff --git a/src/language/helpers/safe-ds-node-mapper.ts b/src/language/helpers/safe-ds-node-mapper.ts index c371a80bc..e4d68a9b5 100644 --- a/src/language/helpers/safe-ds-node-mapper.ts +++ b/src/language/helpers/safe-ds-node-mapper.ts @@ -3,24 +3,35 @@ import { SafeDsTypeComputer } from '../typing/safe-ds-type-computer.js'; import { isSdsAbstractCall, isSdsAnnotationCall, + isSdsBlock, isSdsCall, isSdsCallable, - isSdsClass, - isSdsEnumVariant, isSdsNamedType, + isSdsReference, + isSdsSegment, isSdsType, + isSdsYield, SdsAbstractCall, SdsArgument, SdsCallable, - SdsNamedTypeDeclaration, SdsParameter, + SdsPlaceholder, + SdsReference, + SdsResult, SdsTypeArgument, SdsTypeParameter, + SdsYield, } from '../generated/ast.js'; import { CallableType, StaticType } from '../typing/model.js'; -import { getContainerOfType } from 'langium'; -import { argumentsOrEmpty, parametersOrEmpty, typeArgumentsOrEmpty, typeParametersOrEmpty } from './shortcuts.js'; -import { isNamedArgument, isNamedTypeArgument } from './checks.js'; +import { findLocalReferences, getContainerOfType } from 'langium'; +import { + argumentsOrEmpty, + isNamedArgument, + isNamedTypeArgument, + parametersOrEmpty, + typeArgumentsOrEmpty, + typeParametersOrEmpty, +} from './nodeProperties.js'; export class SafeDsNodeMapper { private readonly typeComputer: () => SafeDsTypeComputer; @@ -29,27 +40,6 @@ export class SafeDsNodeMapper { this.typeComputer = () => services.types.TypeComputer; } - /** - * Returns the callable that is called by the given call. If no callable can be found, returns undefined. - */ - callToCallableOrUndefined(node: SdsAbstractCall | undefined): SdsCallable | undefined { - if (isSdsAnnotationCall(node)) { - return node.annotation?.ref; - } else if (isSdsCall(node)) { - const receiverType = this.typeComputer().computeType(node.receiver); - if (receiverType instanceof CallableType) { - return receiverType.sdsCallable; - } else if (receiverType instanceof StaticType) { - const declaration = receiverType.instanceType.sdsDeclaration; - if (isSdsCallable(declaration)) { - return declaration; - } - } - } - - return undefined; - } - /** * Returns the parameter that the argument is assigned to. If there is no matching parameter, returns undefined. */ @@ -91,6 +81,92 @@ export class SafeDsNodeMapper { return undefined; } + /** + * Returns the callable that is called by the given call. If no callable can be found, returns undefined. + */ + callToCallableOrUndefined(node: SdsAbstractCall | undefined): SdsCallable | undefined { + if (!node) { + return undefined; + } + + if (isSdsAnnotationCall(node)) { + return node.annotation?.ref; + } else if (isSdsCall(node)) { + const receiverType = this.typeComputer().computeType(node.receiver); + if (receiverType instanceof CallableType) { + return receiverType.sdsCallable; + } else if (receiverType instanceof StaticType) { + const declaration = receiverType.instanceType.sdsDeclaration; + if (isSdsCallable(declaration)) { + return declaration; + } + } + } + + return undefined; + } + + /** + * Returns all references that target the given parameter. + */ + parameterToReferences(node: SdsParameter | undefined): SdsReference[] { + if (!node) { + return []; + } + + const containingCallable = getContainerOfType(node, isSdsCallable); + /* c8 ignore start */ + if (!containingCallable) { + return []; + } + /* c8 ignore stop */ + + return findLocalReferences(node, containingCallable) + .map((it) => it.$refNode?.astNode) + .filter(isSdsReference) + .toArray(); + } + + /** + * Returns all references that target the given placeholder. + */ + placeholderToReferences(node: SdsPlaceholder | undefined): SdsReference[] { + if (!node) { + return []; + } + + const containingBlock = getContainerOfType(node, isSdsBlock); + /* c8 ignore start */ + if (!containingBlock) { + return []; + } + /* c8 ignore stop */ + + return findLocalReferences(node, containingBlock) + .map((it) => it.$refNode?.astNode) + .filter(isSdsReference) + .toArray(); + } + + /** + * Returns all yields that assign to the given result. + */ + resultToYields(node: SdsResult | undefined): SdsYield[] { + if (!node) { + return []; + } + + const containingSegment = getContainerOfType(node, isSdsSegment); + if (!containingSegment) { + return []; + } + + return findLocalReferences(node, containingSegment) + .map((it) => it.$refNode?.astNode) + .filter(isSdsYield) + .toArray(); + } + /** * Returns the type parameter that the type argument is assigned to. If there is no matching type parameter, returns * undefined. @@ -123,17 +199,7 @@ export class SafeDsNodeMapper { // Find type parameter at the same position const namedTypeDeclaration = containingType.declaration.ref; - const typeParameters = this.typeParametersOfNamedTypeDeclarationOrEmpty(namedTypeDeclaration); + const typeParameters = typeParametersOrEmpty(namedTypeDeclaration); return typeParameters[typeArgumentPosition]; } - - private typeParametersOfNamedTypeDeclarationOrEmpty(node: SdsNamedTypeDeclaration | undefined): SdsTypeParameter[] { - if (isSdsClass(node)) { - return typeParametersOrEmpty(node.typeParameterList); - } else if (isSdsEnumVariant(node)) { - return typeParametersOrEmpty(node.typeParameterList); - } else { - return []; - } - } } diff --git a/src/language/helpers/shortcuts.ts b/src/language/helpers/shortcuts.ts deleted file mode 100644 index 94766d4ac..000000000 --- a/src/language/helpers/shortcuts.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { - isSdsAssignment, - isSdsBlockLambdaResult, - isSdsDeclaration, - isSdsModule, - isSdsModuleMember, - isSdsPlaceholder, - SdsAbstractCall, - SdsAnnotatedObject, - SdsAnnotationCall, - SdsArgument, - SdsAssignee, - SdsAssignment, - SdsBlock, - SdsBlockLambda, - SdsBlockLambdaResult, - SdsCallable, - SdsClass, - SdsClassMember, - SdsEnum, - SdsEnumVariant, - SdsImport, - SdsImportedDeclaration, - SdsLiteral, - SdsLiteralType, - SdsModule, - SdsModuleMember, - SdsParameter, - SdsPlaceholder, - SdsQualifiedImport, - SdsResult, - SdsResultList, - SdsStatement, - SdsTypeArgument, - SdsTypeArgumentList, - SdsTypeParameter, - SdsTypeParameterList, -} from '../generated/ast.js'; -import { AstNode, getContainerOfType, stream } from 'langium'; - -export const annotationCallsOrEmpty = function (node: SdsAnnotatedObject | undefined): SdsAnnotationCall[] { - if (!node) { - /* c8 ignore next 2 */ - return []; - } - - if (isSdsDeclaration(node)) { - return node?.annotationCallList?.annotationCalls ?? node?.annotationCalls ?? []; - } else { - /* c8 ignore next 2 */ - return node?.annotationCalls ?? []; - } -}; - -export const argumentsOrEmpty = function (node: SdsAbstractCall | undefined): SdsArgument[] { - return node?.argumentList?.arguments ?? []; -}; - -export const assigneesOrEmpty = function (node: SdsAssignment | undefined): SdsAssignee[] { - return node?.assigneeList?.assignees ?? []; -}; - -export const blockLambdaResultsOrEmpty = function (node: SdsBlockLambda | undefined): SdsBlockLambdaResult[] { - return stream(statementsOrEmpty(node?.body)) - .filter(isSdsAssignment) - .flatMap(assigneesOrEmpty) - .filter(isSdsBlockLambdaResult) - .toArray(); -}; - -export const importedDeclarationsOrEmpty = function (node: SdsQualifiedImport | undefined): SdsImportedDeclaration[] { - return node?.importedDeclarationList?.importedDeclarations ?? []; -}; - -export const literalsOrEmpty = function (node: SdsLiteralType | undefined): SdsLiteral[] { - return node?.literalList?.literals ?? []; -}; - -export const classMembersOrEmpty = function ( - node: SdsClass | undefined, - filterFunction: (member: SdsClassMember) => boolean = () => true, -): SdsClassMember[] { - return node?.body?.members?.filter(filterFunction) ?? []; -}; - -export const enumVariantsOrEmpty = function (node: SdsEnum | undefined): SdsEnumVariant[] { - return node?.body?.variants ?? []; -}; - -export const importsOrEmpty = function (node: SdsModule | undefined): SdsImport[] { - return node?.imports ?? []; -}; - -export const moduleMembersOrEmpty = function (node: SdsModule | undefined): SdsModuleMember[] { - return node?.members?.filter(isSdsModuleMember) ?? []; -}; - -export const packageNameOrUndefined = function (node: AstNode | undefined): string | undefined { - return getContainerOfType(node, isSdsModule)?.name; -}; - -export const parametersOrEmpty = function (node: SdsCallable | undefined): SdsParameter[] { - return node?.parameterList?.parameters ?? []; -}; - -export const placeholdersOrEmpty = function (node: SdsBlock | undefined): SdsPlaceholder[] { - return stream(statementsOrEmpty(node)) - .filter(isSdsAssignment) - .flatMap(assigneesOrEmpty) - .filter(isSdsPlaceholder) - .toArray(); -}; - -export const resultsOrEmpty = function (node: SdsResultList | undefined): SdsResult[] { - return node?.results ?? []; -}; - -export const statementsOrEmpty = function (node: SdsBlock | undefined): SdsStatement[] { - return node?.statements ?? []; -}; - -export const typeArgumentsOrEmpty = function (node: SdsTypeArgumentList | undefined): SdsTypeArgument[] { - return node?.typeArguments ?? []; -}; - -export const typeParametersOrEmpty = function (node: SdsTypeParameterList | undefined): SdsTypeParameter[] { - return node?.typeParameters ?? []; -}; diff --git a/src/language/scoping/safe-ds-scope-provider.ts b/src/language/scoping/safe-ds-scope-provider.ts index 108f0a719..c94bc2ebf 100644 --- a/src/language/scoping/safe-ds-scope-provider.ts +++ b/src/language/scoping/safe-ds-scope-provider.ts @@ -47,20 +47,20 @@ import { SdsTypeArgument, SdsYield, } from '../generated/ast.js'; +import { isContainedIn } from '../helpers/astUtils.js'; import { assigneesOrEmpty, classMembersOrEmpty, enumVariantsOrEmpty, importedDeclarationsOrEmpty, importsOrEmpty, + isStatic, packageNameOrUndefined, parametersOrEmpty, resultsOrEmpty, statementsOrEmpty, typeParametersOrEmpty, -} from '../helpers/shortcuts.js'; -import { isContainedIn } from '../helpers/ast.js'; -import { isStatic } from '../helpers/checks.js'; +} from '../helpers/nodeProperties.js'; import { SafeDsServices } from '../safe-ds-module.js'; import { SafeDsTypeComputer } from '../typing/safe-ds-type-computer.js'; import { SafeDsPackageManager } from '../workspace/safe-ds-package-manager.js'; diff --git a/src/language/typing/safe-ds-type-computer.ts b/src/language/typing/safe-ds-type-computer.ts index 79108af60..996be7aaa 100644 --- a/src/language/typing/safe-ds-type-computer.ts +++ b/src/language/typing/safe-ds-type-computer.ts @@ -72,14 +72,14 @@ import { SdsSegment, SdsType, } from '../generated/ast.js'; +import { SafeDsNodeMapper } from '../helpers/safe-ds-node-mapper.js'; import { assigneesOrEmpty, blockLambdaResultsOrEmpty, parametersOrEmpty, resultsOrEmpty, typeArgumentsOrEmpty, -} from '../helpers/shortcuts.js'; -import { SafeDsNodeMapper } from '../helpers/safe-ds-node-mapper.js'; +} from '../helpers/nodeProperties.js'; export class SafeDsTypeComputer { private readonly astNodeLocator: AstNodeLocator; diff --git a/src/language/validation/names.ts b/src/language/validation/names.ts index 1db31fb4e..c87e5b598 100644 --- a/src/language/validation/names.ts +++ b/src/language/validation/names.ts @@ -15,13 +15,14 @@ import { ValidationAcceptor } from 'langium'; import { blockLambdaResultsOrEmpty, classMembersOrEmpty, + enumVariantsOrEmpty, + isStatic, parametersOrEmpty, placeholdersOrEmpty, resultsOrEmpty, typeParametersOrEmpty, - enumVariantsOrEmpty, -} from '../helpers/shortcuts.js'; -import { isStatic } from '../helpers/checks.js'; +} from '../helpers/nodeProperties.js'; +import { duplicatesBy } from '../helpers/collectionUtils.js'; export const CODE_NAME_BLOCK_LAMBDA_PREFIX = 'name/block-lambda-prefix'; export const CODE_NAME_CASING = 'name/casing'; @@ -238,23 +239,11 @@ const namesMustBeUnique = ( createMessage: (name: string) => string, accept: ValidationAcceptor, ): void => { - const knownNames = new Set(); - - for (const node of nodes) { - const name = node.name; - if (!name) { - /* c8 ignore next 2 */ - continue; - } - - if (knownNames.has(name)) { - accept('error', createMessage(name), { - node, - property: 'name', - code: CODE_NAME_DUPLICATE, - }); - } else { - knownNames.add(name); - } + for (const node of duplicatesBy(nodes, (it) => it.name)) { + accept('error', createMessage(node.name), { + node, + property: 'name', + code: CODE_NAME_DUPLICATE, + }); } }; diff --git a/src/language/validation/other/types/callableTypes.ts b/src/language/validation/other/types/callableTypes.ts index 6c7f32024..0ed896090 100644 --- a/src/language/validation/other/types/callableTypes.ts +++ b/src/language/validation/other/types/callableTypes.ts @@ -1,6 +1,7 @@ import { SdsCallableType } from '../../../generated/ast.js'; import { ValidationAcceptor } from 'langium'; -import { parametersOrEmpty } from '../../../helpers/shortcuts.js'; + +import { parametersOrEmpty } from '../../../helpers/nodeProperties.js'; export const CODE_CALLABLE_TYPE_NO_OPTIONAL_PARAMETERS = 'callable-type/no-optional-parameters'; diff --git a/src/language/validation/other/types/unionTypes.ts b/src/language/validation/other/types/unionTypes.ts index ac3a059d3..29ff578c0 100644 --- a/src/language/validation/other/types/unionTypes.ts +++ b/src/language/validation/other/types/unionTypes.ts @@ -1,7 +1,7 @@ import { SdsUnionType } from '../../../generated/ast.js'; import { ValidationAcceptor } from 'langium'; -import { typeArgumentsOrEmpty } from '../../../helpers/shortcuts.js'; import { isEmpty } from 'radash'; +import { typeArgumentsOrEmpty } from '../../../helpers/nodeProperties.js'; export const CODE_UNION_TYPE_MISSING_TYPE_ARGUMENTS = 'union-type/missing-type-arguments'; diff --git a/src/language/workspace/safe-ds-package-manager.ts b/src/language/workspace/safe-ds-package-manager.ts index 4474c8664..72257e9d1 100644 --- a/src/language/workspace/safe-ds-package-manager.ts +++ b/src/language/workspace/safe-ds-package-manager.ts @@ -8,9 +8,8 @@ import { IndexManager, LangiumDocuments, } from 'langium'; -import { packageNameOrUndefined } from '../helpers/shortcuts.js'; import { isSdsSegment } from '../generated/ast.js'; -import { isInternal } from '../helpers/checks.js'; +import { isInternal, packageNameOrUndefined } from '../helpers/nodeProperties.js'; export class SafeDsPackageManager { private readonly astNodeLocator: AstNodeLocator; diff --git a/tests/language/helpers/collectionUtils.test.ts b/tests/language/helpers/collectionUtils.test.ts new file mode 100644 index 000000000..0080023ea --- /dev/null +++ b/tests/language/helpers/collectionUtils.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, it } from 'vitest'; +import { duplicatesBy, uniqueOrUndefined } from '../../../src/language/helpers/collectionUtils.js'; + +describe('duplicatesBy', () => { + const id = (element: any) => element; + + it('should return an empty list given an empty list', () => { + expect([...duplicatesBy([], id)]).toStrictEqual([]); + }); + + it('should keep elements with the same label as a previous one', () => { + expect([...duplicatesBy([1, 2, 1, 3], id)]).toStrictEqual([1]); + }); + + it('should remove elements with an undefined label', () => { + const zeroToUndefined = (element: number) => (element === 0 ? undefined : element); + expect([...duplicatesBy([0, 1, 0], zeroToUndefined)]).toStrictEqual([]); + }); + + it('should remove elements with a unique label', () => { + expect([...duplicatesBy([1, 2, 3], id)]).toStrictEqual([]); + }); +}); + +describe('uniqueOrUndefined', () => { + it('should return undefined if the list is empty', () => { + expect(uniqueOrUndefined([])).toBeUndefined(); + }); + + it('should return the singular element of the list', () => { + expect(uniqueOrUndefined([1])).toBe(1); + }); + + it('should return undefined if the list has multiple elements', () => { + expect(uniqueOrUndefined([1, 2])).toBeUndefined(); + }); +}); diff --git a/tests/language/helpers/idManager.test.ts b/tests/language/helpers/idManager.test.ts new file mode 100644 index 000000000..27d107640 --- /dev/null +++ b/tests/language/helpers/idManager.test.ts @@ -0,0 +1,57 @@ +import { beforeEach, describe, expect, it } from 'vitest'; +import { IdManager } from '../../../src/language/helpers/idManager.js'; + +describe('IdManager', () => { + let idManager: IdManager; + + beforeEach(() => { + idManager = new IdManager(); + }); + + describe('assignId', () => { + it('should assign a new id for new objects', () => { + const obj1 = {}; + const obj2 = {}; + + const id1 = idManager.assignId(obj1); + const id2 = idManager.assignId(obj2); + + expect(id1).toBe(0); + expect(id2).toBe(1); + }); + + it('should return the old id for known objects', () => { + const obj = {}; + + const id1 = idManager.assignId(obj); + const id2 = idManager.assignId(obj); + + expect(id2).toBe(id1); + }); + }); + describe('reset', () => { + it('should forget all objects and ids', () => { + const obj1 = {}; + const obj2 = {}; + + idManager.assignId(obj1); + const id1 = idManager.assignId(obj2); + idManager.reset(); + const id2 = idManager.assignId(obj2); + + expect(id1).to.not.equal(id2); + }); + + it('should reset the id counter', () => { + const obj1 = {}; + const obj2 = {}; + + idManager.assignId(obj1); + idManager.assignId(obj2); + idManager.reset(); + const id = idManager.assignId(obj2); + + expect(id).toBe(0); + }); + }); +}); diff --git a/tests/language/helpers/safe-ds-node-mapper/argumentToParameterOrUndefined.test.ts b/tests/language/helpers/safe-ds-node-mapper/argumentToParameterOrUndefined.test.ts index 9596e590a..f65f6be0b 100644 --- a/tests/language/helpers/safe-ds-node-mapper/argumentToParameterOrUndefined.test.ts +++ b/tests/language/helpers/safe-ds-node-mapper/argumentToParameterOrUndefined.test.ts @@ -4,7 +4,7 @@ import { clearDocuments } from 'langium/test'; import { EmptyFileSystem } from 'langium'; import { getNodeOfType } from '../../../helpers/nodeFinder.js'; import { isSdsAbstractCall, SdsArgument } from '../../../../src/language/generated/ast.js'; -import { argumentsOrEmpty } from '../../../../src/language/helpers/shortcuts.js'; +import { argumentsOrEmpty } from '../../../../src/language/helpers/nodeProperties.js'; const services = createSafeDsServices(EmptyFileSystem).SafeDs; const nodeMapper = services.helpers.NodeMapper; @@ -29,8 +29,8 @@ describe('SafeDsNodeMapper', () => { } `; - const firstCall = await getNodeOfType(services, code, isSdsAbstractCall); - const parameterNames = argumentsOrEmpty(firstCall).map(parameterNameOrNull); + const call = await getNodeOfType(services, code, isSdsAbstractCall); + const parameterNames = argumentsOrEmpty(call).map(parameterNameOrNull); expect(parameterNames).toStrictEqual([undefined]); }); @@ -43,8 +43,8 @@ describe('SafeDsNodeMapper', () => { } `; - const firstCall = await getNodeOfType(services, code, isSdsAbstractCall); - const parameterNames = argumentsOrEmpty(firstCall).map(parameterNameOrNull); + const call = await getNodeOfType(services, code, isSdsAbstractCall); + const parameterNames = argumentsOrEmpty(call).map(parameterNameOrNull); expect(parameterNames).toStrictEqual(['p2', 'p3', 'p1']); }); }); @@ -59,8 +59,8 @@ describe('SafeDsNodeMapper', () => { } `; - const firstCall = await getNodeOfType(services, code, isSdsAbstractCall); - const parameterNames = argumentsOrEmpty(firstCall).map(parameterNameOrNull); + const call = await getNodeOfType(services, code, isSdsAbstractCall); + const parameterNames = argumentsOrEmpty(call).map(parameterNameOrNull); expect(parameterNames).toStrictEqual(['p1', 'p2', 'p3']); }); @@ -73,8 +73,8 @@ describe('SafeDsNodeMapper', () => { } `; - const firstCall = await getNodeOfType(services, code, isSdsAbstractCall); - const parameterNames = argumentsOrEmpty(firstCall).map(parameterNameOrNull); + const call = await getNodeOfType(services, code, isSdsAbstractCall); + const parameterNames = argumentsOrEmpty(call).map(parameterNameOrNull); expect(parameterNames).toStrictEqual(['p2', undefined, undefined]); }); @@ -87,8 +87,8 @@ describe('SafeDsNodeMapper', () => { } `; - const firstCall = await getNodeOfType(services, code, isSdsAbstractCall); - const parameterNames = argumentsOrEmpty(firstCall).map(parameterNameOrNull); + const call = await getNodeOfType(services, code, isSdsAbstractCall); + const parameterNames = argumentsOrEmpty(call).map(parameterNameOrNull); expect(parameterNames).toStrictEqual(['p1', 'p2', undefined]); }); @@ -101,8 +101,8 @@ describe('SafeDsNodeMapper', () => { } `; - const firstCall = await getNodeOfType(services, code, isSdsAbstractCall); - const parameterNames = argumentsOrEmpty(firstCall).map(parameterNameOrNull); + const call = await getNodeOfType(services, code, isSdsAbstractCall); + const parameterNames = argumentsOrEmpty(call).map(parameterNameOrNull); expect(parameterNames).toStrictEqual(['p1', 'p2', 'p3', 'p3', 'p3']); }); }); diff --git a/tests/language/helpers/safe-ds-node-mapper/callToCallableOrUndefined.test.ts b/tests/language/helpers/safe-ds-node-mapper/callToCallableOrUndefined.test.ts index 4ea49098c..b973f8fd5 100644 --- a/tests/language/helpers/safe-ds-node-mapper/callToCallableOrUndefined.test.ts +++ b/tests/language/helpers/safe-ds-node-mapper/callToCallableOrUndefined.test.ts @@ -29,8 +29,8 @@ describe('SafeDsNodeMapper', () => { pipeline myPipeline {} `; - const firstCall = await getNodeOfType(services, code, isSdsAbstractCall); - expect(nodeMapper.callToCallableOrUndefined(firstCall)).toBeUndefined(); + const call = await getNodeOfType(services, code, isSdsAbstractCall); + expect(nodeMapper.callToCallableOrUndefined(call)).toBeUndefined(); }); it('should return the called annotation', async () => { @@ -41,8 +41,8 @@ describe('SafeDsNodeMapper', () => { class MyClass `; - const firstCall = await getNodeOfType(services, code, isSdsAbstractCall); - expect(nodeMapper.callToCallableOrUndefined(firstCall)?.$type).toBe('SdsAnnotation'); + const call = await getNodeOfType(services, code, isSdsAbstractCall); + expect(nodeMapper.callToCallableOrUndefined(call)?.$type).toBe('SdsAnnotation'); }); }); @@ -58,8 +58,8 @@ describe('SafeDsNodeMapper', () => { } `; - const firstCall = await getNodeOfType(services, code, isSdsAbstractCall); - expect(nodeMapper.callToCallableOrUndefined(firstCall)).toBeUndefined(); + const call = await getNodeOfType(services, code, isSdsAbstractCall); + expect(nodeMapper.callToCallableOrUndefined(call)).toBeUndefined(); }); it('should return undefined if receiver is not callable', async () => { @@ -71,8 +71,8 @@ describe('SafeDsNodeMapper', () => { } `; - const firstCall = await getNodeOfType(services, code, isSdsAbstractCall); - expect(nodeMapper.callToCallableOrUndefined(firstCall)).toBeUndefined(); + const call = await getNodeOfType(services, code, isSdsAbstractCall); + expect(nodeMapper.callToCallableOrUndefined(call)).toBeUndefined(); }); it('should return the called annotation', async () => { @@ -84,8 +84,8 @@ describe('SafeDsNodeMapper', () => { } `; - const firstCall = await getNodeOfType(services, code, isSdsAbstractCall); - expect(nodeMapper.callToCallableOrUndefined(firstCall)?.$type).toBe('SdsAnnotation'); + const call = await getNodeOfType(services, code, isSdsAbstractCall); + expect(nodeMapper.callToCallableOrUndefined(call)?.$type).toBe('SdsAnnotation'); }); it('should return the called annotation (aliased)', async () => { @@ -98,8 +98,8 @@ describe('SafeDsNodeMapper', () => { } `; - const firstCall = await getNodeOfType(services, code, isSdsAbstractCall); - expect(nodeMapper.callToCallableOrUndefined(firstCall)?.$type).toBe('SdsAnnotation'); + const call = await getNodeOfType(services, code, isSdsAbstractCall); + expect(nodeMapper.callToCallableOrUndefined(call)?.$type).toBe('SdsAnnotation'); }); it('should return the called block lambda (aliased)', async () => { @@ -110,8 +110,8 @@ describe('SafeDsNodeMapper', () => { } `; - const firstCall = await getNodeOfType(services, code, isSdsAbstractCall); - expect(nodeMapper.callToCallableOrUndefined(firstCall)?.$type).toBe('SdsBlockLambda'); + const call = await getNodeOfType(services, code, isSdsAbstractCall); + expect(nodeMapper.callToCallableOrUndefined(call)?.$type).toBe('SdsBlockLambda'); }); it('should return the called callable type', async () => { @@ -121,8 +121,8 @@ describe('SafeDsNodeMapper', () => { } `; - const firstCall = await getNodeOfType(services, code, isSdsAbstractCall); - expect(nodeMapper.callToCallableOrUndefined(firstCall)?.$type).toBe('SdsCallableType'); + const call = await getNodeOfType(services, code, isSdsAbstractCall); + expect(nodeMapper.callToCallableOrUndefined(call)?.$type).toBe('SdsCallableType'); }); it('should return the called callable type (aliased)', async () => { @@ -133,8 +133,8 @@ describe('SafeDsNodeMapper', () => { } `; - const firstCall = await getNodeOfType(services, code, isSdsAbstractCall); - expect(nodeMapper.callToCallableOrUndefined(firstCall)?.$type).toBe('SdsCallableType'); + const call = await getNodeOfType(services, code, isSdsAbstractCall); + expect(nodeMapper.callToCallableOrUndefined(call)?.$type).toBe('SdsCallableType'); }); it('should return the called class', async () => { @@ -146,8 +146,8 @@ describe('SafeDsNodeMapper', () => { } `; - const firstCall = await getNodeOfType(services, code, isSdsAbstractCall); - expect(nodeMapper.callToCallableOrUndefined(firstCall)?.$type).toBe('SdsClass'); + const call = await getNodeOfType(services, code, isSdsAbstractCall); + expect(nodeMapper.callToCallableOrUndefined(call)?.$type).toBe('SdsClass'); }); it('should return the called class (aliased)', async () => { @@ -160,8 +160,8 @@ describe('SafeDsNodeMapper', () => { } `; - const firstCall = await getNodeOfType(services, code, isSdsAbstractCall); - expect(nodeMapper.callToCallableOrUndefined(firstCall)?.$type).toBe('SdsClass'); + const call = await getNodeOfType(services, code, isSdsAbstractCall); + expect(nodeMapper.callToCallableOrUndefined(call)?.$type).toBe('SdsClass'); }); it('should return the called enum variant', async () => { @@ -175,8 +175,8 @@ describe('SafeDsNodeMapper', () => { } `; - const firstCall = await getNodeOfType(services, code, isSdsAbstractCall); - expect(nodeMapper.callToCallableOrUndefined(firstCall)?.$type).toBe('SdsEnumVariant'); + const call = await getNodeOfType(services, code, isSdsAbstractCall); + expect(nodeMapper.callToCallableOrUndefined(call)?.$type).toBe('SdsEnumVariant'); }); it('should return the called enum variant (aliased)', async () => { @@ -191,8 +191,8 @@ describe('SafeDsNodeMapper', () => { } `; - const firstCall = await getNodeOfType(services, code, isSdsAbstractCall); - expect(nodeMapper.callToCallableOrUndefined(firstCall)?.$type).toBe('SdsEnumVariant'); + const call = await getNodeOfType(services, code, isSdsAbstractCall); + expect(nodeMapper.callToCallableOrUndefined(call)?.$type).toBe('SdsEnumVariant'); }); it('should return the called expression lambda (aliased)', async () => { @@ -203,8 +203,8 @@ describe('SafeDsNodeMapper', () => { } `; - const firstCall = await getNodeOfType(services, code, isSdsAbstractCall); - expect(nodeMapper.callToCallableOrUndefined(firstCall)?.$type).toBe('SdsExpressionLambda'); + const call = await getNodeOfType(services, code, isSdsAbstractCall); + expect(nodeMapper.callToCallableOrUndefined(call)?.$type).toBe('SdsExpressionLambda'); }); it('should return the called function', async () => { @@ -216,8 +216,8 @@ describe('SafeDsNodeMapper', () => { } `; - const firstCall = await getNodeOfType(services, code, isSdsAbstractCall); - expect(nodeMapper.callToCallableOrUndefined(firstCall)?.$type).toBe('SdsFunction'); + const call = await getNodeOfType(services, code, isSdsAbstractCall); + expect(nodeMapper.callToCallableOrUndefined(call)?.$type).toBe('SdsFunction'); }); it('should return the called function (aliased)', async () => { @@ -230,8 +230,8 @@ describe('SafeDsNodeMapper', () => { } `; - const firstCall = await getNodeOfType(services, code, isSdsAbstractCall); - expect(nodeMapper.callToCallableOrUndefined(firstCall)?.$type).toBe('SdsFunction'); + const call = await getNodeOfType(services, code, isSdsAbstractCall); + expect(nodeMapper.callToCallableOrUndefined(call)?.$type).toBe('SdsFunction'); }); it('should return the called segment', async () => { @@ -243,8 +243,8 @@ describe('SafeDsNodeMapper', () => { } `; - const firstCall = await getNodeOfType(services, code, isSdsAbstractCall); - expect(nodeMapper.callToCallableOrUndefined(firstCall)?.$type).toBe('SdsSegment'); + const call = await getNodeOfType(services, code, isSdsAbstractCall); + expect(nodeMapper.callToCallableOrUndefined(call)?.$type).toBe('SdsSegment'); }); it('should return the called segment (aliased)', async () => { @@ -257,8 +257,8 @@ describe('SafeDsNodeMapper', () => { } `; - const firstCall = await getNodeOfType(services, code, isSdsAbstractCall); - expect(nodeMapper.callToCallableOrUndefined(firstCall)?.$type).toBe('SdsSegment'); + const call = await getNodeOfType(services, code, isSdsAbstractCall); + expect(nodeMapper.callToCallableOrUndefined(call)?.$type).toBe('SdsSegment'); }); }); }); diff --git a/tests/language/helpers/safe-ds-node-mapper/parameterToReferences.test.ts b/tests/language/helpers/safe-ds-node-mapper/parameterToReferences.test.ts new file mode 100644 index 000000000..e0cebbaea --- /dev/null +++ b/tests/language/helpers/safe-ds-node-mapper/parameterToReferences.test.ts @@ -0,0 +1,68 @@ +import { afterEach, describe, expect, it } from 'vitest'; +import { createSafeDsServices } from '../../../../src/language/safe-ds-module.js'; +import { clearDocuments } from 'langium/test'; +import { EmptyFileSystem } from 'langium'; +import { getNodeOfType } from '../../../helpers/nodeFinder.js'; +import { isSdsParameter } from '../../../../src/language/generated/ast.js'; + +const services = createSafeDsServices(EmptyFileSystem).SafeDs; +const nodeMapper = services.helpers.NodeMapper; + +describe('SafeDsNodeMapper', () => { + afterEach(async () => { + await clearDocuments(services); + }); + + describe('parameterToReferences', () => { + it('should return an empty list if passed undefined', async () => { + expect(nodeMapper.parameterToReferences(undefined)).toStrictEqual([]); + }); + + it('should return references in default values', async () => { + const code = ` + fun myFunction(p1: Int, p2: Int = p1) + `; + + const parameter = await getNodeOfType(services, code, isSdsParameter); + expect(nodeMapper.parameterToReferences(parameter)).toHaveLength(1); + }); + + it('should return references directly in body', async () => { + const code = ` + segment mySegment(p1: Int) { + p1; + p1; + }; + `; + + const parameter = await getNodeOfType(services, code, isSdsParameter); + expect(nodeMapper.parameterToReferences(parameter)).toHaveLength(2); + }); + + it('should return references nested in body', async () => { + const code = ` + segment mySegment(p1: Int) { + () { + p1; + }; + () -> p1; + }; + `; + + const parameter = await getNodeOfType(services, code, isSdsParameter); + expect(nodeMapper.parameterToReferences(parameter)).toHaveLength(2); + }); + + it('should not return references to other parameters', async () => { + const code = ` + segment mySegment(p1: Int, p2: Int) { + p1; + p2; + }; + `; + + const parameter = await getNodeOfType(services, code, isSdsParameter); + expect(nodeMapper.parameterToReferences(parameter)).toHaveLength(1); + }); + }); +}); diff --git a/tests/language/helpers/safe-ds-node-mapper/placeholdersToReferences.test.ts b/tests/language/helpers/safe-ds-node-mapper/placeholdersToReferences.test.ts new file mode 100644 index 000000000..8caffb349 --- /dev/null +++ b/tests/language/helpers/safe-ds-node-mapper/placeholdersToReferences.test.ts @@ -0,0 +1,78 @@ +import { afterEach, describe, expect, it } from 'vitest'; +import { createSafeDsServices } from '../../../../src/language/safe-ds-module.js'; +import { clearDocuments } from 'langium/test'; +import { EmptyFileSystem } from 'langium'; +import { getNodeOfType } from '../../../helpers/nodeFinder.js'; +import { isSdsPlaceholder } from '../../../../src/language/generated/ast.js'; + +const services = createSafeDsServices(EmptyFileSystem).SafeDs; +const nodeMapper = services.helpers.NodeMapper; + +describe('SafeDsNodeMapper', () => { + afterEach(async () => { + await clearDocuments(services); + }); + + describe('placeholderToReferences', () => { + it('should return an empty list if passed undefined', async () => { + expect(nodeMapper.placeholderToReferences(undefined)).toStrictEqual([]); + }); + + it('should return references in default values', async () => { + const code = ` + segment mySegment() { + val a1 = 1; + (p1: Int = a1) -> 1; + } + `; + + const placeholder = await getNodeOfType(services, code, isSdsPlaceholder); + expect(nodeMapper.placeholderToReferences(placeholder)).toHaveLength(1); + }); + + it('should return references directly in body', async () => { + const code = ` + pipeline myPipeline { + val a1 = 1; + + a1; + a1; + }; + `; + + const placeholder = await getNodeOfType(services, code, isSdsPlaceholder); + expect(nodeMapper.placeholderToReferences(placeholder)).toHaveLength(2); + }); + + it('should return references nested in body', async () => { + const code = ` + segment mySegment() { + val a1 = 1; + + () { + a1; + }; + () -> a1; + }; + `; + + const placeholder = await getNodeOfType(services, code, isSdsPlaceholder); + expect(nodeMapper.placeholderToReferences(placeholder)).toHaveLength(2); + }); + + it('should not return references to other placeholders', async () => { + const code = ` + pipeline myPipeline { + val a1 = 1; + val a2 = 2; + + a1; + a2; + }; + `; + + const placeholder = await getNodeOfType(services, code, isSdsPlaceholder); + expect(nodeMapper.placeholderToReferences(placeholder)).toHaveLength(1); + }); + }); +}); diff --git a/tests/language/helpers/safe-ds-node-mapper/resultToYields.test.ts b/tests/language/helpers/safe-ds-node-mapper/resultToYields.test.ts new file mode 100644 index 000000000..289920025 --- /dev/null +++ b/tests/language/helpers/safe-ds-node-mapper/resultToYields.test.ts @@ -0,0 +1,53 @@ +import { afterEach, describe, expect, it } from 'vitest'; +import { createSafeDsServices } from '../../../../src/language/safe-ds-module.js'; +import { clearDocuments } from 'langium/test'; +import { EmptyFileSystem } from 'langium'; +import { getNodeOfType } from '../../../helpers/nodeFinder.js'; +import { isSdsResult } from '../../../../src/language/generated/ast.js'; + +const services = createSafeDsServices(EmptyFileSystem).SafeDs; +const nodeMapper = services.helpers.NodeMapper; + +describe('SafeDsNodeMapper', () => { + afterEach(async () => { + await clearDocuments(services); + }); + + describe('resultToYields', () => { + it('should return an empty list if passed undefined', async () => { + expect(nodeMapper.resultToYields(undefined)).toStrictEqual([]); + }); + + it('should return an empty list if result is not in a segment', async () => { + const code = `fun myFunction() -> r1: Int`; + + const result = await getNodeOfType(services, code, isSdsResult); + expect(nodeMapper.resultToYields(result)).toStrictEqual([]); + }); + + it('should return all yields that refer to a result', async () => { + const code = ` + segment mySegment() -> r1: Int { + yield r1 = 1; + yield r1 = 2; + } + `; + + const result = await getNodeOfType(services, code, isSdsResult); + expect(nodeMapper.resultToYields(result)).toHaveLength(2); + }); + + it('should not return yields that refer to another result', async () => { + const code = ` + segment mySegment() -> (r1: Int, r2: Int) { + yield r1 = 1; + yield r2 = 2; + yield r2 = 3; + } + `; + + const result = await getNodeOfType(services, code, isSdsResult); + expect(nodeMapper.resultToYields(result)).toHaveLength(1); + }); + }); +}); diff --git a/tests/language/helpers/safe-ds-node-mapper/typeArgumentToTypeParameterOrUndefined.test.ts b/tests/language/helpers/safe-ds-node-mapper/typeArgumentToTypeParameterOrUndefined.test.ts index ee1b8e1dd..36a7eb39f 100644 --- a/tests/language/helpers/safe-ds-node-mapper/typeArgumentToTypeParameterOrUndefined.test.ts +++ b/tests/language/helpers/safe-ds-node-mapper/typeArgumentToTypeParameterOrUndefined.test.ts @@ -4,7 +4,7 @@ import { clearDocuments } from 'langium/test'; import { EmptyFileSystem } from 'langium'; import { getNodeOfType } from '../../../helpers/nodeFinder.js'; import { isSdsNamedType, isSdsUnionType, SdsTypeArgument } from '../../../../src/language/generated/ast.js'; -import { typeArgumentsOrEmpty } from '../../../../src/language/helpers/shortcuts.js'; +import { typeArgumentsOrEmpty } from '../../../../src/language/helpers/nodeProperties.js'; const services = createSafeDsServices(EmptyFileSystem).SafeDs; const nodeMapper = services.helpers.NodeMapper; @@ -27,10 +27,8 @@ describe('SafeDsNodeMapper', () => { segment mySegment(p: Unresolved) {} `; - const firstNamedType = await getNodeOfType(services, code, isSdsNamedType); - const parameterNames = typeArgumentsOrEmpty(firstNamedType.typeArgumentList).map( - typeParameterNameOrNull, - ); + const namedType = await getNodeOfType(services, code, isSdsNamedType); + const parameterNames = typeArgumentsOrEmpty(namedType.typeArgumentList).map(typeParameterNameOrNull); expect(parameterNames).toStrictEqual([undefined]); }); @@ -41,10 +39,8 @@ describe('SafeDsNodeMapper', () => { segment mySegment(p: C) {} `; - const firstNamedType = await getNodeOfType(services, code, isSdsNamedType); - const parameterNames = typeArgumentsOrEmpty(firstNamedType.typeArgumentList).map( - typeParameterNameOrNull, - ); + const namedType = await getNodeOfType(services, code, isSdsNamedType); + const parameterNames = typeArgumentsOrEmpty(namedType.typeArgumentList).map(typeParameterNameOrNull); expect(parameterNames).toStrictEqual([undefined]); }); @@ -55,10 +51,8 @@ describe('SafeDsNodeMapper', () => { segment mySegment(p: C) {} `; - const firstNamedType = await getNodeOfType(services, code, isSdsNamedType); - const parameterNames = typeArgumentsOrEmpty(firstNamedType.typeArgumentList).map( - typeParameterNameOrNull, - ); + const namedType = await getNodeOfType(services, code, isSdsNamedType); + const parameterNames = typeArgumentsOrEmpty(namedType.typeArgumentList).map(typeParameterNameOrNull); expect(parameterNames).toStrictEqual(['T2', 'T3', 'T1']); }); }); @@ -71,10 +65,8 @@ describe('SafeDsNodeMapper', () => { segment mySegment(p: Unresolved) {} `; - const firstNamedType = await getNodeOfType(services, code, isSdsNamedType); - const parameterNames = typeArgumentsOrEmpty(firstNamedType.typeArgumentList).map( - typeParameterNameOrNull, - ); + const namedType = await getNodeOfType(services, code, isSdsNamedType); + const parameterNames = typeArgumentsOrEmpty(namedType.typeArgumentList).map(typeParameterNameOrNull); expect(parameterNames).toStrictEqual([undefined]); }); @@ -87,10 +79,8 @@ describe('SafeDsNodeMapper', () => { segment mySegment(p: E.V) {} `; - const firstNamedType = await getNodeOfType(services, code, isSdsNamedType, 1); - const parameterNames = typeArgumentsOrEmpty(firstNamedType.typeArgumentList).map( - typeParameterNameOrNull, - ); + const namedType = await getNodeOfType(services, code, isSdsNamedType, 1); + const parameterNames = typeArgumentsOrEmpty(namedType.typeArgumentList).map(typeParameterNameOrNull); expect(parameterNames).toStrictEqual(['T1', 'T2', 'T3']); }); @@ -101,10 +91,8 @@ describe('SafeDsNodeMapper', () => { segment mySegment(p: C) {} `; - const firstNamedType = await getNodeOfType(services, code, isSdsNamedType); - const parameterNames = typeArgumentsOrEmpty(firstNamedType.typeArgumentList).map( - typeParameterNameOrNull, - ); + const namedType = await getNodeOfType(services, code, isSdsNamedType); + const parameterNames = typeArgumentsOrEmpty(namedType.typeArgumentList).map(typeParameterNameOrNull); expect(parameterNames).toStrictEqual(['T2', undefined, undefined]); }); @@ -115,10 +103,8 @@ describe('SafeDsNodeMapper', () => { segment mySegment(p: C) {} `; - const firstNamedType = await getNodeOfType(services, code, isSdsNamedType); - const parameterNames = typeArgumentsOrEmpty(firstNamedType.typeArgumentList).map( - typeParameterNameOrNull, - ); + const namedType = await getNodeOfType(services, code, isSdsNamedType); + const parameterNames = typeArgumentsOrEmpty(namedType.typeArgumentList).map(typeParameterNameOrNull); expect(parameterNames).toStrictEqual(['T1', 'T2', undefined]); }); @@ -129,10 +115,8 @@ describe('SafeDsNodeMapper', () => { segment mySegment(p: union) {} `; - const firstNamedType = await getNodeOfType(services, code, isSdsUnionType); - const parameterNames = typeArgumentsOrEmpty(firstNamedType.typeArgumentList).map( - typeParameterNameOrNull, - ); + const unionType = await getNodeOfType(services, code, isSdsUnionType); + const parameterNames = typeArgumentsOrEmpty(unionType.typeArgumentList).map(typeParameterNameOrNull); expect(parameterNames).toStrictEqual([undefined, undefined]); }); @@ -143,10 +127,8 @@ describe('SafeDsNodeMapper', () => { segment mySegment(p: C>) {} `; - const firstNamedType = await getNodeOfType(services, code, isSdsUnionType); - const parameterNames = typeArgumentsOrEmpty(firstNamedType.typeArgumentList).map( - typeParameterNameOrNull, - ); + const unionType = await getNodeOfType(services, code, isSdsUnionType); + const parameterNames = typeArgumentsOrEmpty(unionType.typeArgumentList).map(typeParameterNameOrNull); expect(parameterNames).toStrictEqual([undefined, undefined]); }); });