1+ import { invariant } from '@zenstackhq/common-helpers' ;
12import { AstUtils , type AstNode , type DiagnosticInfo , type ValidationAcceptor } from 'langium' ;
23import { IssueCodes , SCALAR_TYPES } from '../constants' ;
34import {
@@ -16,8 +17,8 @@ import {
1617} from '../generated/ast' ;
1718import {
1819 getAllAttributes ,
20+ getAllFields ,
1921 getLiteral ,
20- getModelFieldsWithBases ,
2122 getModelIdFields ,
2223 getModelUniqueFields ,
2324 getUniqueFields ,
@@ -32,7 +33,7 @@ import { validateDuplicatedDeclarations, type AstValidator } from './common';
3233 */
3334export default class DataModelValidator implements AstValidator < DataModel > {
3435 validate ( dm : DataModel , accept : ValidationAcceptor ) : void {
35- validateDuplicatedDeclarations ( dm , getModelFieldsWithBases ( dm ) , accept ) ;
36+ validateDuplicatedDeclarations ( dm , getAllFields ( dm ) , accept ) ;
3637 this . validateAttributes ( dm , accept ) ;
3738 this . validateFields ( dm , accept ) ;
3839 if ( dm . mixins . length > 0 ) {
@@ -42,7 +43,7 @@ export default class DataModelValidator implements AstValidator<DataModel> {
4243 }
4344
4445 private validateFields ( dm : DataModel , accept : ValidationAcceptor ) {
45- const allFields = getModelFieldsWithBases ( dm ) ;
46+ const allFields = getAllFields ( dm ) ;
4647 const idFields = allFields . filter ( ( f ) => f . attributes . find ( ( attr ) => attr . decl . ref ?. name === '@id' ) ) ;
4748 const uniqueFields = allFields . filter ( ( f ) => f . attributes . find ( ( attr ) => attr . decl . ref ?. name === '@unique' ) ) ;
4849 const modelLevelIds = getModelIdFields ( dm ) ;
@@ -266,7 +267,7 @@ export default class DataModelValidator implements AstValidator<DataModel> {
266267 const oppositeModel = field . type . reference ! . ref ! as DataModel ;
267268
268269 // Use name because the current document might be updated
269- let oppositeFields = getModelFieldsWithBases ( oppositeModel , false ) . filter (
270+ let oppositeFields = getAllFields ( oppositeModel , false ) . filter (
270271 ( f ) =>
271272 f !== field && // exclude self in case of self relation
272273 f . type . reference ?. ref ?. name === contextModel . name ,
@@ -438,11 +439,38 @@ export default class DataModelValidator implements AstValidator<DataModel> {
438439 if ( ! model . baseModel ) {
439440 return ;
440441 }
441- if ( model . baseModel . ref && ! isDelegateModel ( model . baseModel . ref ) ) {
442+
443+ invariant ( model . baseModel . ref , 'baseModel must be resolved' ) ;
444+
445+ // check if the base model is a delegate model
446+ if ( ! isDelegateModel ( model . baseModel . ref ) ) {
442447 accept ( 'error' , `Model ${ model . baseModel . $refText } cannot be extended because it's not a delegate model` , {
443448 node : model ,
444449 property : 'baseModel' ,
445450 } ) ;
451+ return ;
452+ }
453+
454+ // check for cyclic inheritance
455+ const seen : DataModel [ ] = [ ] ;
456+ const todo = [ model . baseModel . ref ] ;
457+ while ( todo . length > 0 ) {
458+ const current = todo . shift ( ) ! ;
459+ if ( seen . includes ( current ) ) {
460+ accept (
461+ 'error' ,
462+ `Cyclic inheritance detected: ${ seen . map ( ( m ) => m . name ) . join ( ' -> ' ) } -> ${ current . name } ` ,
463+ {
464+ node : model ,
465+ } ,
466+ ) ;
467+ return ;
468+ }
469+ seen . push ( current ) ;
470+ if ( current . baseModel ) {
471+ invariant ( current . baseModel . ref , 'baseModel must be resolved' ) ;
472+ todo . push ( current . baseModel . ref ) ;
473+ }
446474 }
447475 }
448476
0 commit comments