Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions packages/language/src/zmodel-scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)),
);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

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) {
Expand Down
Loading