diff --git a/packages/language/src/zmodel-scope.ts b/packages/language/src/zmodel-scope.ts index 8dd3c4dab..655dc8b04 100644 --- a/packages/language/src/zmodel-scope.ts +++ b/packages/language/src/zmodel-scope.ts @@ -17,11 +17,15 @@ import { } from 'langium'; import { match } from 'ts-pattern'; import { + AttributeArg, BinaryExpr, + DataModel, Expression, MemberAccessExpr, + isAttributeArg, isCollectionPredicateBinding, isDataField, + isDataFieldAttribute, isDataModel, isEnumField, isInvocationExpr, @@ -132,6 +136,13 @@ export class ZModelScopeProvider extends DefaultScopeProvider { if (containerCollectionPredicate) { return this.getCollectionPredicateScope(context, containerCollectionPredicate); } + + // @relation(fields: [], references: []) + // `references` should include the target model's fields instead of the current model's + const targetModel = this.getTransitiveFieldReferenceTargetModel(context); + if (targetModel) { + return this.getTransitiveFieldReferenceScope(targetModel); + } } return super.getScope(context); @@ -264,6 +275,46 @@ export class ZModelScopeProvider extends DefaultScopeProvider { .otherwise(() => EMPTY_SCOPE); } + private getTransitiveFieldReferenceScope(dm: DataModel) { + return this.createScope( + getAllFields(dm).map((field) => this.descriptions.createDescription(field, field.name)), + ); + } + + private getTransitiveFieldReferenceTargetModel(context: ReferenceInfo) { + let current: AstNode | undefined = context.container; + while (current) { + if (isAttributeArg(current)) { + const attr = current.$container; + if (!isDataFieldAttribute(attr)) { + return undefined; + } + + const decl = attr.decl.ref; + if (!decl) { + return undefined; + } + + const param = current.name + ? decl.params.find((p) => p.name === (current as AttributeArg).name) + : decl.params[attr.args.indexOf(current)]; + if (param?.type?.type !== 'TransitiveFieldReference') { + return undefined; + } + + const field = attr.$container; + if (!isDataField(field)) { + return undefined; + } + + const targetType = field.type?.reference?.ref; + return isDataModel(targetType) ? targetType : undefined; + } + current = current.$container; + } + return undefined; + } + private createScopeForContainingModel(node: AstNode, globalScope: Scope) { const model = AstUtils.getContainerOfType(node, isDataModel); if (model) {