Skip to content

Commit 3bd749b

Browse files
committed
feat(dts-generator): add TypedJSONModel - trying, wip, do not submit
Add the type definitions for the TypedJSONModel and adapt the generator to insert them to the core d.ts file.
1 parent b6c0fe5 commit 3bd749b

File tree

5 files changed

+560
-3
lines changed

5 files changed

+560
-3
lines changed

packages/dts-generator/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,12 @@
5050
},
5151
"scripts": {
5252
"clean": "del-cli -f dist",
53-
"copy-files": "copyfiles -V -u 1 \"src/**/*.json\" \"src/**/core-preamble.d.ts\" \"src/**/dtslintConfig/.npm___ignore\" \"src/**/dtslintConfig/openui5-tests.ts\" \"src/**/dtslintConfig/.eslintrc.json\" \"src/**/dtslintConfig/forDefinitelyTypedDir/.eslintrc.cjs\" \"src/**/api-json.d.ts\" dist/",
53+
"copy-files": "copyfiles -V -u 1 \"src/**/*.json\" \"src/**/core-preamble.d.ts\" \"src/**/typed-json-model.d.ts\" \"src/**/dtslintConfig/.npm___ignore\" \"src/**/dtslintConfig/openui5-tests.ts\" \"src/**/dtslintConfig/.eslintrc.json\" \"src/**/dtslintConfig/forDefinitelyTypedDir/.eslintrc.cjs\" \"src/**/api-json.d.ts\" dist/",
5454
"prebuild": "npm-run-all clean copy-files",
5555
"build": "tsc",
5656
"postbuild": "npm-run-all build-api-types clean-implementation-types",
5757
"build-api-types": "api-extractor run --local --verbose",
58-
"clean-implementation-types": "del-cli -f \"dist/**/*.d.ts.map\" \"dist/**/*.d.ts\" \"!dist/**/index.d.ts\" \"!dist/**/core-preamble.d.ts\"",
58+
"clean-implementation-types": "del-cli -f \"dist/**/*.d.ts.map\" \"dist/**/*.d.ts\" \"!dist/**/index.d.ts\" \"!dist/**/core-preamble.d.ts\" \"!dist/**/typed-json-model.d.ts\"",
5959
"ci": "npm-run-all test:*",
6060
"test:apis": "tsc ./src/types/api-json.d.ts ./src/types/ast.d.ts ./src/types/ui5-logger-types.d.ts",
6161
"prewatch": "npm-run-all clean copy-files",

packages/dts-generator/src/phases/post-process.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,39 @@ export async function postProcess(
2929
);
3030
}
3131

32+
// add the TypedJSONModel - but only if we do not generate globals
33+
if (!options.generateGlobals) {
34+
const typedJsonModel = fs
35+
.readFileSync(
36+
new URL("../resources/typed-json-model.d.ts", import.meta.url),
37+
)
38+
.toString();
39+
40+
// the position right after JSONModel
41+
let pos = dtsResult.dtsText.indexOf(
42+
'declare module "sap/ui/model/json/JSONPropertyBinding" {',
43+
);
44+
// or at least before JSONModel
45+
if (pos === -1) {
46+
pos = dtsResult.dtsText.indexOf(
47+
'declare module "sap/ui/model/json/JSONModel" {',
48+
);
49+
}
50+
if (pos > -1) {
51+
// insert the typedJsonModel after/before the JSONModel module declaration in case we found it
52+
dtsResult.dtsText = await reformat(
53+
dtsResult.dtsText.slice(0, pos) +
54+
typedJsonModel +
55+
"\n" +
56+
dtsResult.dtsText.slice(pos),
57+
);
58+
} else {
59+
// otherwise just append it to the end of the dtsText
60+
dtsResult.dtsText += typedJsonModel;
61+
}
62+
}
63+
64+
// prepend preamble
3265
dtsResult.dtsText = await reformat(preamble + dtsResult.dtsText);
3366
break;
3467

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
declare module "sap/ui/model/json/TypedJSONModel" {
2+
import JSONModel from "sap/ui/model/json/JSONModel";
3+
import TypedJSONContext from "sap/ui/model/json/TypedJSONContext";
4+
5+
/**
6+
* TypedJSONModel is a subclass of JSONModel that provides type-safe access to the model data. It is only available when using UI5 with TypeScript.
7+
*
8+
* @since 1.138.0
9+
*/
10+
export default class TypedJSONModel<Data extends object> extends JSONModel {
11+
constructor(oData?: Data, bObserve?: boolean);
12+
13+
getData(): Data;
14+
15+
getProperty<Path extends AbsoluteBindingPath<Data>>(
16+
sPath: Path,
17+
): PropertyByAbsoluteBindingPath<Data, Path>;
18+
getProperty<
19+
Path extends RelativeBindingPath<Data, Root>,
20+
Root extends AbsoluteBindingPath<Data>,
21+
>(
22+
sPath: Path,
23+
oContext: TypedJSONContext<Data, Root>,
24+
): PropertyByRelativeBindingPath<Data, Root, Path>;
25+
getProperty<
26+
Path extends AbsoluteBindingPath<Data> | RelativeBindingPath<Data, Root>,
27+
Root extends AbsoluteBindingPath<Data>,
28+
>(
29+
sPath: Path,
30+
oContext?: TypedJSONContext<Data, Root>,
31+
):
32+
| PropertyByAbsoluteBindingPath<Data, Path>
33+
| PropertyByRelativeBindingPath<Data, Root, Path>;
34+
35+
setData(data: Data): void;
36+
37+
setProperty<Path extends AbsoluteBindingPath<Data>>(
38+
sPath: Path,
39+
oValue: PropertyByAbsoluteBindingPath<Data, Path>,
40+
oContext?: undefined,
41+
bAsyncUpdate?: boolean,
42+
): boolean;
43+
setProperty<
44+
Path extends RelativeBindingPath<Data, Root>,
45+
Root extends AbsoluteBindingPath<Data>,
46+
>(
47+
sPath: Path extends RelativeBindingPath<Data, Root> ? Path : never,
48+
oValue: PropertyByRelativeBindingPath<Data, Root, Path>,
49+
oContext: TypedJSONContext<Data, Root>,
50+
bAsyncUpdate?: boolean,
51+
): boolean;
52+
setProperty<
53+
Path extends AbsoluteBindingPath<Data> | RelativeBindingPath<Data, Root>,
54+
Root extends AbsoluteBindingPath<Data>,
55+
>(
56+
sPath: Path,
57+
oValue: Path extends AbsoluteBindingPath<Data>
58+
? PropertyByAbsoluteBindingPath<Data, Path>
59+
: PropertyByRelativeBindingPath<Data, Root, Path>,
60+
oContext?: TypedJSONContext<Data, Root>,
61+
bAsyncUpdate?: boolean,
62+
): boolean;
63+
}
64+
65+
/**
66+
* Valid absolute binding in a JSONModel with the underlying type `Type`.
67+
* Counterpart to {@link PropertyByAbsoluteBindingPath}
68+
* @example
69+
* type Person = { name: string, id: number };
70+
* type PersonNamePath = PathInJSONModel<Person>; // "/name" | "/id"
71+
* let path: PersonNamePath = "/name"; // ok
72+
* path = "/firstName"; // error
73+
*/
74+
export type AbsoluteBindingPath<Type> =
75+
Type extends Array<unknown>
76+
? // if Type is an array:
77+
| `/${number}` // /0 -> first element of array
78+
| `/${number}${AbsoluteBindingPath<Type[number]>}` // /0/{NestedPath}
79+
: // if Type is not an array:
80+
Type extends object
81+
?
82+
| {
83+
[Key in keyof Type]: Type[Key] extends Array<unknown>
84+
? // Type[Key] is an array:
85+
| `/${string & Key}/${number}` // items/0 -> elem of array
86+
// path can end there or:
87+
| `/${string & Key}/${number}${AbsoluteBindingPath<Type[Key][number]>}` // items/0/{NestedPath}
88+
: // Type[Key] is NOT an array:
89+
`/${string & Key}${AbsoluteBindingPath<Type[Key]>}`;
90+
}[keyof Type]
91+
| `/${string & PropertiesOf<Type>}` // /items/0/id -> last part of path
92+
: // if T is not of type object:
93+
never;
94+
95+
/**
96+
* Valid relative binding path in a JSONModel.
97+
* The root of the path is defined by the given root string.
98+
*
99+
* @example
100+
* type PersonWrapper = { person: { name: string, id: number } };
101+
* type PersonNamePath = RelativeBindingPath<PersonWrapper, "/person">; // "name" | "id"
102+
*/
103+
export type RelativeBindingPath<
104+
Type,
105+
Root extends AbsoluteBindingPath<Type>,
106+
> =
107+
AbsoluteBindingPath<TypeAtPath<Type, Root>> extends `/${infer Rest}`
108+
? Rest
109+
: never;
110+
111+
/**
112+
* The type of a property in a JSONModel identified by the given path.
113+
* Counterpart to {@link AbsoluteBindingPath}.
114+
* @example
115+
* type Person = { name: string, id: number };
116+
* type PersonName = PropertyInJSONModel<Person, "/name">; // string
117+
* const name: PersonName = "John"; // ok
118+
*/
119+
export type PropertyByAbsoluteBindingPath<
120+
Type,
121+
Path extends string,
122+
> = Path extends `/${number}`
123+
? Type extends Array<infer U>
124+
? U
125+
: never
126+
: Path extends `/${number}${infer Rest}`
127+
? Type extends Array<infer U>
128+
? PropertyByAbsoluteBindingPath<U, Rest>
129+
: never
130+
: Path extends `/${infer Key}/${number}/${infer Rest}`
131+
? Key extends keyof Type
132+
? FromArrayWithSubPath<Type, Key, Rest>
133+
: never
134+
: Path extends `/${infer Key}/${number}`
135+
? Key extends keyof Type
136+
? FromArrayElement<Type, Key>
137+
: never
138+
: Path extends `/${infer Key}/${infer Rest}`
139+
? Key extends keyof Type
140+
? FromNestedProperty<Type, Key, Rest>
141+
: never
142+
: Path extends `/${infer Key}`
143+
? Key extends keyof Type
144+
? FromTopLevelProperty<Type, Key>
145+
: never
146+
: never;
147+
148+
/**
149+
* The type of a property in a JSONModel identified by the given relative path and root.
150+
* Counterpart to {@link RelativeBindingPath}.
151+
* @example
152+
* type PersonWrapper = { person: { name: string, id: number } };
153+
* type PersonName = PropertyByRelativeBindingPath<PersonWrapper, "/person", "name">;
154+
* const name: PersonName = "John"; // ok
155+
*/
156+
export type PropertyByRelativeBindingPath<
157+
Type,
158+
Root extends string,
159+
RelativePath extends string,
160+
> = PropertyByAbsoluteBindingPath<Type, `${Root}/${RelativePath}`>;
161+
162+
/***********************************************************************************************************************
163+
* Helper types to split the types above into separate parts
164+
* to make it easier to read and understand.
165+
/**********************************************************************************************************************/
166+
167+
/**
168+
* Helper type to handle paths that point to an array with a subpath.
169+
* @example const path = "/orders/0/items"
170+
*/
171+
type FromArrayWithSubPath<Type, Key extends keyof Type, Rest extends string> =
172+
Type[Key] extends Array<infer U>
173+
? PropertyByAbsoluteBindingPath<U, `/${Rest}`>
174+
: never;
175+
176+
/**
177+
* Helper type to handle paths that point to an array element.
178+
* @example const path = "/orders/0"
179+
*/
180+
type FromArrayElement<Type, Key extends keyof Type> =
181+
Type[Key] extends Array<infer U> ? U : never;
182+
183+
/**
184+
* Helper type to handle paths that point to a nested property.
185+
* @example const path = "/customer/address/street"
186+
*/
187+
type FromNestedProperty<
188+
Type,
189+
Key extends keyof Type,
190+
Rest extends string,
191+
> = PropertyByAbsoluteBindingPath<Type[Key], `/${Rest}`>;
192+
193+
/**
194+
* Helper type to handle paths that point to a top-level property.
195+
* @example const path = "/customer"
196+
*/
197+
type FromTopLevelProperty<Type, Key extends keyof Type> = Type[Key];
198+
199+
/**
200+
* Helper type to navigate along a nested path.
201+
* Navigates from the root type along the path to determine the sub-type.
202+
*/
203+
type TypeAtPath<
204+
Type,
205+
Path extends string,
206+
> = Path extends `/${infer Key}/${infer Rest}`
207+
? Key extends keyof Type
208+
? TypeAtPath<Type[Key], `/${Rest}`>
209+
: never
210+
: Path extends `/${infer Key}`
211+
? Key extends keyof Type
212+
? Type[Key]
213+
: never
214+
: never;
215+
216+
/**
217+
* Helper type to extract the names of the properties of a given type.
218+
* Excludes properties that are of the type `Function` or `symbol`.
219+
* @example
220+
* type Person = { name: string, id: number };
221+
* type PersonProperties = PropertiesOf<Person>; // "name" | "id"
222+
* let property: PersonProperties = "name"; // ok
223+
* property = "firstName"; // error
224+
*/
225+
type PropertiesOf<Type> = {
226+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
227+
[Key in keyof Type]: Type[Key] extends Function
228+
? never
229+
: Type[Key] extends symbol
230+
? never
231+
: Key;
232+
}[keyof Type];
233+
}
234+
235+
declare module "sap/ui/model/json/TypedJSONContext" {
236+
import Context from "sap/ui/model/Context";
237+
import TypedJSONModel from "sap/ui/model/json/TypedJSONModel";
238+
import {
239+
AbsoluteBindingPath,
240+
RelativeBindingPath,
241+
PropertyByRelativeBindingPath,
242+
} from "sap/ui/model/json/TypedJSONModel";
243+
244+
/**
245+
* TypedJSONContext is a subclass of Context that provides type-safe access to the model data. It is only available when using UI5 with TypeScript.
246+
*
247+
* @since 1.138.0
248+
*/
249+
export default class TypedJSONContext<
250+
Data extends object,
251+
Root extends AbsoluteBindingPath<Data>,
252+
> extends Context {
253+
constructor(oModel: TypedJSONModel<Data>, sPath: Root);
254+
255+
getModel(): TypedJSONModel<Data>;
256+
257+
getProperty<P extends RelativeBindingPath<Data, Root>>(
258+
sPath: P extends RelativeBindingPath<Data, Root> ? P : never,
259+
): PropertyByRelativeBindingPath<Data, Root, P>;
260+
}
261+
}

packages/dts-generator/tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"./node_modules/**/*",
1818
"./src/checkDtslint/dtslintConfig/openui5-tests.ts",
1919
"./src/checkDtslint/dtslintConfig/tsconfig.json",
20-
"./src/resources/core-preamble.d.ts"
20+
"./src/resources/core-preamble.d.ts",
21+
"./src/resources/typed-json-model.d.ts"
2122
]
2223
}

0 commit comments

Comments
 (0)