Skip to content
Closed
Show file tree
Hide file tree
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
26 changes: 26 additions & 0 deletions packages/dts-generator/src/resources/typed-json-model.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { AbsoluteTreeBindingPath } from "../../../../test-packages/typed-json-model/webapp/model/typing";

declare module "sap/ui/model/json/TypedJSONModel" {
import JSONModel from "sap/ui/model/json/JSONModel";
import JSONTreeBinding from "sap/ui/model/json/JSONTreeBinding";
import Filter from "sap/ui/model/Filter";
import Sorter from "sap/ui/model/Sorter";
import TypedJSONContext from "sap/ui/model/json/TypedJSONContext";
import Context from "sap/ui/model/Context";

Expand All @@ -18,6 +23,27 @@ declare module "sap/ui/model/json/TypedJSONModel" {
fnCallBack?: Function,
bReload?: boolean,
): TypedJSONContext<Data, Path>;

// Overload for absolute paths
bindTree<Path extends AbsoluteTreeBindingPath<Data>>(
sPath: Path,
oContext?: undefined,
aFilters?: Filter | Filter[],
mParameters?: object,
aSorters?: Sorter | Sorter[],
): JSONTreeBinding;
// Overload for relative paths
bindTree<
Path extends RelativeTreeBindingPath<Data, Root>,
Root extends AbsoluteBindingPath<Data>,
>(
sPath: Path,
oContext: TypedJSONContext<Data, Root>,
aFilters?: Filter | Filter[],
mParameters?: object,
aSorters?: Sorter | Sorter[],
): JSONTreeBinding;

getData(): Data;
getProperty<Path extends AbsoluteBindingPath<Data>>(
sPath: Path,
Expand Down
2 changes: 1 addition & 1 deletion test-packages/typed-json-model/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"ci": "npm run lint && npm run ui5lint && npm run ts-typecheck && npm run test"
},
"devDependencies": {
"@types/openui5": "1.136.0",
"@openui5/types": "^1.146.0",
"@ui5/cli": "^4.0.30",
"@ui5/linter": "^1.20.2",
"eslint": "^9.37.0",
Expand Down
3 changes: 2 additions & 1 deletion test-packages/typed-json-model/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"baseUrl": "./",
"paths": {},
"composite": true,
"outDir": "./dist"
"outDir": "./dist",
"types": ["@openui5/types"]
},
"include": ["./webapp/**/*"],
"exclude": ["./**/*.mjs", "./webapp/**/test/**"]
Expand Down
35 changes: 35 additions & 0 deletions test-packages/typed-json-model/webapp/model/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@ import Context from "sap/ui/model/Context";
import JSONModel from "sap/ui/model/json/JSONModel";
import {
AbsoluteBindingPath,
AbsoluteTreeBindingPath,
PropertyByAbsoluteBindingPath,
PropertyByRelativeBindingPath,
RelativeBindingPath,
RelativeTreeBindingPath,
} from "./typing";
import Filter from "sap/ui/model/Filter";
import Sorter from "sap/ui/model/Sorter";
import JSONTreeBinding from "sap/ui/model/json/JSONTreeBinding";

export class TypedJSONContext<Data extends object, Root extends AbsoluteBindingPath<Data>> extends Context {
constructor(oModel: TypedJSONModel<Data>, sPath: Root) {
Expand Down Expand Up @@ -39,6 +44,36 @@ export class TypedJSONModel<Data extends object> extends JSONModel {
return super.createBindingContext(sPath, oContext, mParameters, fnCallBack, bReload) as TypedJSONContext<Data, Path>;
}

// Overload for absolute paths
bindTree<Path extends AbsoluteTreeBindingPath<Data>>(
sPath: Path,
oContext?: undefined,
aFilters?: Filter | Filter[],
mParameters?: object,
aSorters?: Sorter | Sorter[],
): JSONTreeBinding;
// Overload for relative paths
bindTree<Path extends RelativeTreeBindingPath<Data, Root>, Root extends AbsoluteBindingPath<Data>>(
sPath: Path,
oContext: TypedJSONContext<Data, Root>,
aFilters?: Filter | Filter[],
mParameters?: object,
aSorters?: Sorter | Sorter[],
): JSONTreeBinding;
// Implementation
bindTree<
Path extends AbsoluteTreeBindingPath<Data> | RelativeTreeBindingPath<Data, Root>,
Root extends AbsoluteBindingPath<Data>,
>(
sPath: Path,
oContext?: TypedJSONContext<Data, Root>,
aFilters?: Filter | Filter[],
mParameters?: object,
aSorters?: Sorter | Sorter[],
): JSONTreeBinding {
return super.bindTree(sPath, oContext, aFilters, mParameters, aSorters);
}

getData(): Data {
return super.getData() as Data;
}
Expand Down
36 changes: 36 additions & 0 deletions test-packages/typed-json-model/webapp/model/test/cases/general.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* @file Various general test cases to test the TypedJSONModel for APIs which always return the same type,
* regardless of the provided path (e.g. getObject, getPath, etc.)
*/

import { TypedJSONModel } from "../../model";
import JSONTreeBinding from "sap/ui/model/json/JSONTreeBinding";

/***********************************************************************************************************************
* bindTree - Absolute cases
**********************************************************************************************************************/

const data = { root: { array: [1, 2, 3], nested: { value: "test" }, number: 1, string: "foo" } };
const model0 = new TypedJSONModel(data);

/** @expect ok */ let jsonTreeBindingAbsolute: JSONTreeBinding = model0.bindTree("/root/array");
/** @expect ok */ jsonTreeBindingAbsolute = model0.bindTree("/root/nested");
/** @expect ts2345 */ jsonTreeBindingAbsolute = model0.bindTree("/root/number");
/** @expect ts2345 */ jsonTreeBindingAbsolute = model0.bindTree("/root/string");
/** @expect ts2345 */ jsonTreeBindingAbsolute = model0.bindTree("/root/nonExisting");
/** @expect ts2345 */ jsonTreeBindingAbsolute = model0.bindTree("/root/array/0");
/** @expect ts2345 */ jsonTreeBindingAbsolute = model0.bindTree("/root/nested/value");

/***********************************************************************************************************************
* bindTree - Relative cases
**********************************************************************************************************************/

const context = model0.createBindingContext("/root");

/** @expect ok */ let jsonTreeBindingRelative: JSONTreeBinding = model0.bindTree("array", context);
/** @expect ok */ jsonTreeBindingRelative = model0.bindTree("nested", context);
/** @expect ts2769 */ jsonTreeBindingRelative = model0.bindTree("number", context);
/** @expect ts2769 */ jsonTreeBindingRelative = model0.bindTree("string", context);
/** @expect ts2769 */ jsonTreeBindingRelative = model0.bindTree("nonExisting", context);
/** @expect ts2769 */ jsonTreeBindingRelative = model0.bindTree("array/0", context);
/** @expect ts2769 */ jsonTreeBindingRelative = model0.bindTree("nested/value", context);
35 changes: 35 additions & 0 deletions test-packages/typed-json-model/webapp/model/typing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,25 @@ export type AbsoluteBindingPath<Type> =
: // if T is not of type object:
never;

/**
* Valid absolute binding path for underlying `Array` or `object` types.
*
* @example
* type SalesOrder = { id: string, items: string[], parameters: { weight: number } };
* type PathInObject = PathInJSONModel<SalesOrder>; // "/id" | "/items" | "/parameters"
* let path: PathInObject = "/items"; // ok
* path = "/parameters"; // ok
* path = "/id"; // error
* path = "/items/0"; // error, since an element in the array is a string
*/
export type AbsoluteTreeBindingPath<Type> = {
[Path in AbsoluteBindingPath<Type>]: PropertyByAbsoluteBindingPath<Type, Path> extends Array<unknown>
? Path
: PropertyByAbsoluteBindingPath<Type, Path> extends object
? Path
: never;
}[AbsoluteBindingPath<Type>];

/**
* Valid relative binding path in a JSONModel.
* The root of the path is defined by the given root string.
Expand Down Expand Up @@ -87,6 +106,22 @@ export type PropertyByRelativeBindingPath<
RelativePath extends string,
> = PropertyByAbsoluteBindingPath<Type, `${Root}/${RelativePath}`>;

/**
* Valid relative binding path for underlying `Array` or `object` types.
* The root of the path is defined by the given root string.
*
* @example
* type SalesOrder = { buyer: { id: string, items: string[], parameters: { weight: number } } };
* type PathRelativeToSalesOrder = RelativeTreeBindingPath<SalesOrderWrapper, "/buyer">; // "items" | "parameters"
*/
export type RelativeTreeBindingPath<Type, Root extends AbsoluteBindingPath<Type>> = {
[Path in RelativeBindingPath<Type, Root>]: PropertyByRelativeBindingPath<Type, Root, Path> extends Array<unknown>
? Path
: PropertyByRelativeBindingPath<Type, Root, Path> extends object
? Path
: never;
}[RelativeBindingPath<Type, Root>];

/***********************************************************************************************************************
* Helper types to split the types above into separate parts
* to make it easier to read and understand.
Expand Down
63 changes: 14 additions & 49 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2361,6 +2361,14 @@
dependencies:
"@octokit/openapi-types" "^24.2.0"

"@openui5/types@^1.146.0":
version "1.146.0"
resolved "https://registry.yarnpkg.com/@openui5/types/-/types-1.146.0.tgz#f3f035bc0f4e25f23acfeba888b927347fb3b336"
integrity sha512-6jzQ54BIpOQEL8+46u2WsaR9gFcjyEaKphUlAS82Pt60OOW8p2xpmTknFRRZDgBmP4IbZZob9nLb82MNGMRTSw==
dependencies:
"@types/jquery" "3.5.13"
"@types/qunit" "2.5.4"

"@pkgjs/parseargs@^0.11.0":
version "0.11.0"
resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
Expand Down Expand Up @@ -2811,14 +2819,6 @@
"@types/jquery" "~3.5.13"
"@types/qunit" "^2.5.4"

"@types/openui5@1.136.0":
version "1.136.0"
resolved "https://registry.yarnpkg.com/@types/openui5/-/openui5-1.136.0.tgz#9cada8f12d5d03d03f4975553cb763da2e7fa104"
integrity sha512-gdjK8/bYKsdZUiinARbYW+B6sbAVo0B4KLbHMs/noBzx2wfUoHOb9DiS+lpBpjtfD28NyiluReCp2R4kp7fceA==
dependencies:
"@types/jquery" "~3.5.13"
"@types/qunit" "^2.5.4"

"@types/qunit@2.5.4":
version "2.5.4"
resolved "https://registry.yarnpkg.com/@types/qunit/-/qunit-2.5.4.tgz#0518940acc6013259a8619a1ec34ce0e4ff8d1c4"
Expand Down Expand Up @@ -3008,7 +3008,7 @@
yargs "^17.7.2"

"@ui5/dts-generator@link:packages/dts-generator":
version "3.9.1"
version "3.10.1"
dependencies:
"@definitelytyped/dtslint" latest
"@definitelytyped/eslint-plugin" latest
Expand Down Expand Up @@ -10948,7 +10948,7 @@ string-length@^4.0.2:
char-regex "^1.0.2"
strip-ansi "^6.0.0"

"string-width-cjs@npm:string-width@^4.2.0":
"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
Expand All @@ -10966,15 +10966,6 @@ string-width@^1.0.1:
is-fullwidth-code-point "^1.0.0"
strip-ansi "^3.0.0"

"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"

string-width@^5.0.1, string-width@^5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
Expand Down Expand Up @@ -11062,7 +11053,7 @@ string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"

"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
Expand All @@ -11076,13 +11067,6 @@ strip-ansi@^3.0.0, strip-ansi@^3.0.1:
dependencies:
ansi-regex "^2.0.0"

strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"

strip-ansi@^7.0.1, strip-ansi@^7.1.0:
version "7.1.2"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.2.tgz#132875abde678c7ea8d691533f2e7e22bb744dba"
Expand Down Expand Up @@ -11561,7 +11545,7 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78"
integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==

"typescript-5.3@npm:typescript@~5.3.0-0":
"typescript-5.3@npm:typescript@~5.3.0-0", typescript@5.3.3:
version "5.3.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37"
integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==
Expand Down Expand Up @@ -11591,7 +11575,7 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e"
integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==

"typescript-5.9@npm:typescript@~5.9.0-0":
"typescript-5.9@npm:typescript@~5.9.0-0", typescript@5.9.3, "typescript@>=3 < 6", typescript@^5.9.3:
version "5.9.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f"
integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==
Expand All @@ -11611,21 +11595,11 @@ typescript-eslint@^8.46.1:
"@typescript-eslint/typescript-estree" "8.46.1"
"@typescript-eslint/utils" "8.46.1"

typescript@5.3.3:
version "5.3.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37"
integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==

typescript@5.8.2:
version "5.8.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.2.tgz#8170b3702f74b79db2e5a96207c15e65807999e4"
integrity sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==

typescript@5.9.3, "typescript@>=3 < 6", typescript@^5.9.3:
version "5.9.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f"
integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==

uc.micro@^2.0.0, uc.micro@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-2.1.0.tgz#f8d3f7d0ec4c3dea35a7e3c8efa4cb8b45c9e7ee"
Expand Down Expand Up @@ -12075,7 +12049,7 @@ workerpool@^9.2.0, workerpool@^9.3.4:
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-9.3.4.tgz#f6c92395b2141afd78e2a889e80cb338fe9fca41"
integrity sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg==

"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
Expand All @@ -12093,15 +12067,6 @@ wrap-ansi@^6.2.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"

wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"

wrap-ansi@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
Expand Down
Loading