cap-js / cds-typer

CDS type generator for JavaScript
Apache License 2.0
25 stars 8 forks source link

[BUG][Regression] Incorrect types generated with composition of many #225

Open tsteckenborn opened 2 months ago

tsteckenborn commented 2 months ago

Is there an existing issue for this?

Nature of Your Project

TypeScript

Current Behavior

(Could be related to earlier behavior in #183)

Given a definition such as:

service TestService {
    entity testNamespace.Test {
        key id              : String;
            testComposition : Composition of many {
                                  key id : String;
                              };
    }

    entity Test as projection on testNamespace.Test;
}

Leads in the @cds-models/TestService/index.ts to:

// ...
  return class Test extends Base {
        id?: string;
        testComposition?: __.Composition.of.many<__.DeepRequired<TestService.testNamespace.Test>['testComposition']>;
      static readonly actions: Record<never, never>
  };
}
// ...

With <__.DeepRequired<TestService < --- Cannot find namespace 'TestService'.ts(2503)

Expected Behavior

Would've expected the generated types be correct.

Steps To Reproduce

See above

Environment

- **OS**: MacOS
- **Node**: 20.12.2
- **npm**: /
- **cds-typer**: 20.0.1
- **cds**: 7.8.1

Repository Containing a Minimal Reproducible Example

No response

Anything else?

No response

tsteckenborn commented 2 months ago

There seems to be a second issue with regards to composition of many.

Having:

entity Test.UserInterfaces {
    key id            : String;
        testProperty1 : Composition of many {
                            test : String;
                        };
        testProperty2 : Composition of many {
                            test : String;
                        };
}

service TestService {
    entity UserInterfaces as projection on Test.UserInterfaces;
}

yields the following:

[...]
export function _UserInterfaceAspect<TBase extends new (...args: any[]) => object>(Base: TBase) {
  return class UserInterface extends Base {
        id?: string;
        usageStatus?: __.Composition.of.many<__.DeepRequired<sap.test.TestService.UserInterface>['usageStatus']>;
        intentMapping?: __.Composition.of.many<__.DeepRequired<sap.test.TestService.UserInterface>['intentMapping']>;
      static readonly actions: Record<never, never>
  };
}
export class UserInterface extends _UserInterfaceAspect(__.Entity) {}
Object.defineProperty(UserInterface, 'name', { value: 'sap.test.TestService.UserInterfaces' })
export class UserInterfaces extends Array<UserInterface> {$count?: number}
Object.defineProperty(UserInterfaces, 'name', { value: 'sap.test.TestService.UserInterfaces' })

export namespace UserInterfaces {
  export function _usageStatuAspect<TBase extends new (...args: any[]) => object>(Base: TBase) {
    return class usageStatu extends Base {
            up_?: __.Association.to<_sap_test_Test.UserInterface>;
            up__id?: string;
            contextContent?: string | null;
        static readonly actions: Record<never, never>
    };
  }
  export class usageStatu extends _usageStatuAspect(__.Entity) {}
  Object.defineProperty(usageStatu, 'name', { value: 'sap.test.Test.UserInterfaces.usageStatus' })
  export class UserInterfaces extends Array<usageStatu> {$count?: number}
  Object.defineProperty(UserInterfaces, 'name', { value: 'sap.test.Test.UserInterfaces.usageStatus' })

  export function _intentMappingAspect<TBase extends new (...args: any[]) => object>(Base: TBase) {
    return class intentMapping extends Base {
            up_?: __.Association.to<_sap_test_Test.UserInterface>;
            up__id?: string;
            externalLinkOpenInNewTab?: boolean | null;
        static readonly actions: Record<never, never>
    };
  }
  export class intentMapping extends _intentMappingAspect(__.Entity) {}
  Object.defineProperty(intentMapping, 'name', { value: 'sap.test.Test.UserInterfaces.intentMapping' })
  export class UserInterfaces extends Array<intentMapping> {$count?: number}
  Object.defineProperty(UserInterfaces, 'name', { value: 'sap.test.Test.UserInterfaces.intentMapping' })

}

With the pluralized versions appearing to be named as the containing entity:

[...]
  export class usageStatu extends _usageStatuAspect(__.Entity) {}
  Object.defineProperty(usageStatu, 'name', { value: 'sap.test.Test.UserInterfaces.usageStatus' })
  export class UserInterfaces extends Array<usageStatu> {$count?: number}
  Object.defineProperty(UserInterfaces, 'name', { value: 'sap.test.Test.UserInterfaces.usageStatus' })
[...]
  export class intentMapping extends _intentMappingAspect(__.Entity) {}
  Object.defineProperty(intentMapping, 'name', { value: 'sap.test.Test.UserInterfaces.intentMapping' })
  export class UserInterfaces extends Array<intentMapping> {$count?: number}
  Object.defineProperty(UserInterfaces, 'name', { value: 'sap.test.Test.UserInterfaces.intentMapping' })
[...]

Which a) seems to incorrect and b) yields the type error Duplicate identifier 'UserInterfaces'.ts(2300)

tsteckenborn commented 2 months ago

The bottom one seems to be due to: https://github.com/cap-js/cds-typer/blob/3120af3b62104aa5c5b69b0d65b13c4a1eadc1c4/lib/components/resolver.js#L315

Here it's running as typeInfo.plainName which is then for these ones:

{
  typeName: undefined,
  singular: 'usageStatu',
  plural: 'UserInterfaces'
}
{
  'typeInfo.csn.name': '[...].UserInterfaces.usageStatus'
}
{ 'typeInfo.plainName': 'UserInterfaces' }
{
  typeName: undefined,
  singular: 'intentMapping',
  plural: 'UserInterfaces'
},
{
  'typeInfo.csn.name': '[...].UserInterfaces.intentMapping'
}
{ 'typeInfo.plainName': 'UserInterfaces' }

So the fallback here (plainName) seems to not work properly.

// Edit:

I assume it boils down to https://github.com/cap-js/cds-typer/blob/3120af3b62104aa5c5b69b0d65b13c4a1eadc1c4/lib/components/resolver.js#L173 where it's seen as e.g.:

fq: [...someService].UserInterfaces.intentMapping
ns: [...someService]
property: intentMapping
nameParts: UserInterfaces
scope: 
name: UserInterfaces

Due to util.getPluralAnnotation(typeInfo.csn) being undefined in then falls back to UserInterfaces as that's set as the "plainType", which then results in the name being used when creating the classes with the pluralized names.

prophet1906 commented 1 month ago

I am facing the same issue. The issue is reproducible for >= 0.20.0.

github-actions[bot] commented 3 weeks ago

This issue has not been updated in a while. If it is still relevant, please comment on it to keep it open. The issue will be closed soon if it remains inactive.