Skip to content

Commit 76e4a3b

Browse files
committed
Finished first readme draft
1 parent f3cc312 commit 76e4a3b

File tree

5 files changed

+321
-64
lines changed

5 files changed

+321
-64
lines changed

README.md

+239
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# UI5TypescriptDefinitionParser
2+
23
Parses Typescript from the UI5 documentation
34

45
## Usage
@@ -7,3 +8,241 @@ Parses Typescript from the UI5 documentation
78

89
1. Go to console and run `npm install` (make sure node.js is installed and referenced in PATH <- Windows only)
910
1. Install gulp (`npm install gulp -g`)
11+
12+
### Use
13+
14+
Create a nodejs server application and import the module Parser:
15+
16+
```typescript
17+
import { Parser } from "./Parser";
18+
19+
let p = new Parser("./config.json");
20+
return p.GenerateDeclarations();
21+
```
22+
23+
## Settings
24+
25+
All settings are configured in the config.json file, which path can be given to the parser.
26+
27+
### 1. Fetching API.json
28+
29+
The parser will first try to get locally cached files. If it does not find any it will try and get them from the hana.ondemand source.
30+
31+
```json
32+
"connection": {
33+
"root": "https://openui5.hana.ondemand.com/test-resources",
34+
"endpoints": [
35+
"sap/m/designtime/api.json",
36+
"sap/ui/core/designtime/api.json",
37+
"sap/tnt/designtime/api.json",
38+
"sap/ui/commons/designtime/api.json",
39+
"sap/ui/demokit/designtime/api.json",
40+
"sap/ui/dt/designtime/api.json",
41+
"sap/ui/layout/designtime/api.json",
42+
"sap/ui/suite/designtime/api.json",
43+
"sap/ui/table/designtime/api.json",
44+
"sap/ui/unified/designtime/api.json",
45+
"sap/ui/ux3/designtime/api.json",
46+
"sap/uxap/designtime/api.json"
47+
]
48+
},
49+
```
50+
51+
You can find these files after the first run in the `apis` folder generated by the parser. You can prevent caching by setting it to false:
52+
53+
```json
54+
"cacheApis": true,
55+
```
56+
57+
### 2. Preprocessing
58+
59+
The preprocessing will modify the loaded api.json objects (no files are modified) using the [jsonpath package](https://www.npmjs.com/package/jsonpath). Thus, it uses json path to get the desired parameters specified by `jsonpath`. In the subscript filter javascript can be used (for example regex).
60+
61+
After that it will apply the function given in `script`. The scriptfunction always gets a val variable, which has to be manipulated and **returned**. The logger will output the caught object in debug level or set a breakpoint in the debugwrapper in the parser.ts, if you are running it with a debugger.
62+
63+
In addition a comment can be provided to describe what is happening in the log output.
64+
65+
This example will set all methods except extend and getMetadata for all modules starting with `sap/m/` to not static:
66+
67+
```json
68+
"preProcessing": {
69+
"correctStaticSapM": {
70+
"comment": "Replaces incorrect static values in sap/m",
71+
"script": "if(val.name) val.static = false;",
72+
"jsonpath":
73+
"$.symbols[?(@.kind == 'class' && @.module.startsWith('sap/m/'))].methods[?(@.static == true && @.name.match(/^(?!extend|getMetadata).*$/))]"
74+
}, ...
75+
}
76+
```
77+
78+
### 3. Parsing
79+
80+
The parsing process goes through the loaded api docs, takes all exported symbols and combines them to valid typings. Via config you have multiple ways to adjust the process.
81+
82+
#### 3.1 Types and parameter name replacements / fixes
83+
84+
To fix some bugs in parameter names (for example usage of html tags or other not-allowed figures for variable and parameter names) the `cleanParamNames` section can be used:
85+
86+
```json
87+
"cleanParamNames": {
88+
"<code>vData</code>": "vData",
89+
"&lt;your-page-object-name&gt;": "[key: string]"
90+
},
91+
```
92+
93+
The property name is the parameter to replace and gets replaced by the value (which has to be a string). Example:
94+
95+
```typescript
96+
class a {
97+
public testMethod(<code>vData</code>: string): void;
98+
}
99+
```
100+
101+
The method above would not be compilable for the parser. Using the replacement, the `<code>vData</code>` will be replaced with the proper `"vData"` name:
102+
103+
```typescript
104+
class a {
105+
public testMethod(vData: string): void;
106+
}
107+
```
108+
109+
This will be applied to all function/method parameters in all classes, namespaces, etc.
110+
111+
#### 3.2 Type Map
112+
113+
The type map is the same as the `cleanParameterNames`. It will replace wrong types, in all classes, namespaces, enums, etc. The key will be the type to replace, the value will be the replacement
114+
115+
```json
116+
"typeMap": {
117+
"*": "any",
118+
"any[]": "any[]",
119+
"array": "any[]",
120+
```
121+
122+
Example:
123+
124+
```typescript
125+
class a {
126+
public testMethod(data: *): void;
127+
}
128+
```
129+
130+
will be replaced by
131+
132+
```typescript
133+
class a {
134+
public testMethod(data: any): void;
135+
}
136+
```
137+
138+
#### 3.3 Parsing enums
139+
140+
Enums will be parsed using the template `enums.d.ts.hb`. The template uses [Handlebars](https://handlebarsjs.com/) syntax and the raw symbol:
141+
142+
```typescript
143+
interface ISymbol {
144+
// enum
145+
kind: string;
146+
// sap.m.BackgroundDesign
147+
name: string;
148+
// BackgroundDesign
149+
basename: string;
150+
// sap/m/library.js
151+
resource: string;
152+
// sap/m/library or sap.m.library if not modular
153+
module: string;
154+
// BackgroundDesign
155+
export: string;
156+
// true
157+
static: boolean;
158+
// public, private, recstricted or protected
159+
visibility: string;
160+
// Description text
161+
description: string;
162+
// The properties the enum uses
163+
properties: IProperty[];
164+
}
165+
166+
interface IProperty {
167+
// Solid
168+
name: string;
169+
// public, private, restricted or protected
170+
visibility: string;
171+
// true or false
172+
static: boolean;
173+
// any type, basetype or other sap.ui.... type
174+
type: string;
175+
// any description
176+
description: string;
177+
}
178+
```
179+
180+
The files will all be put out in the out folder and enums subfolder. All enums will be declared ambient.
181+
182+
#### 3.4 Parsing namespaces
183+
184+
Uses `ParsedNamespace` class and `namespace.d.ts.hb` template. All namespaces are ambient (thus, always available).
185+
186+
#### 3.5 Parsing interfaces
187+
188+
Uses `ParsedClass` class and `interface.d.ts.hb` template. All interfaces are ambient (thus, always available).
189+
190+
#### 3.6 Parsing classes
191+
192+
Uses `ParsedClass` class and either `classAmbient.d.ts.hb` template, if the class is ambient (`module` of Symbol in the api json is separated with dots `.` instead of slashes `/`), otherwise it uses `classModule.d.ts.hb`.
193+
194+
##### a) Establishing Inheritance
195+
196+
After getting all classes (that means the ParsedClass objects are instanciated), the parser will start with base classes that have no base class and work its way up until the inheritance hierarchy is established.
197+
198+
##### b) Creating Overloads
199+
200+
After the class inheritance is done, the parser will start to get all overloaded methods and try to adjust them so they can be overloaded. Furthermore leading optional parameters are resolved as overloads. Tailing optional method parameters are left with `?`.
201+
202+
### 4. Postprocessing
203+
204+
Postprocessing will be going through all output files and replace whole text sections either using strings or regular expressions. The key will be the path to the file starting from the output folder. The value is an array with all replacements that should be done in the file.
205+
206+
Example for a string:
207+
208+
```json
209+
"namespaces/sap.ui.core.BusyIndicator": [
210+
{
211+
"searchString": "declare namespace {} {",
212+
"replacement": "declare namespace sap.ui.core.BusyIndicator {"
213+
}
214+
],
215+
```
216+
217+
Example for a regex:
218+
219+
```json
220+
"classes/sap/ui/model/Model": [
221+
{
222+
"searchString": "(\\W)Model([^\\w\\/\\.'`<])",
223+
"replacement": "$1Model<T>$2",
224+
"isRegex": true,
225+
"regexFlags": "g"
226+
}
227+
]
228+
```
229+
230+
to replace with a regex `isRegex` has to be set to true and optionally a flag can be set (as the global flag above).
231+
232+
Also globs can be used:
233+
234+
```json
235+
"namespaces/**/*": [
236+
{
237+
"searchString":
238+
"sap\\.ui\\.core\\.(?:Control|Element|BusyIndicator|Component|mvc\\.Controller|mvc\\.View|mvc\\.Fragment|Fragment|Core|mvc\\.HTMLView|mvc\\.JSONView|mvc\\.JSView|tmpl\\.Template|mvc\\.TemplateView|mvc\\.XMLView)([^\\w])",
239+
"replacement": "{}$1",
240+
"isRegex": true,
241+
"regexFlags": "g"
242+
},
243+
]
244+
```
245+
246+
### 5. Custom Handlebars extensions
247+
248+
To put in some custom handlebars extensions just modify the file `handlebarsExtensions.ts`

gulpfile.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export class Gulpfile {
4848
@Task()
4949
runTest() {
5050
let p = new Parser("./config.json");
51-
return p.GenerateDeclarations("declarations");
51+
return p.GenerateDeclarations();
5252
}
5353

5454
@Task()

src/Parser.ts

+43-37
Original file line numberDiff line numberDiff line change
@@ -48,45 +48,48 @@ export class Parser implements ILogDecorator {
4848
private currentApi: IApi;
4949
private logger: Log;
5050

51-
private interfaceTemplate = Handlebars.compile(
52-
fs.readFileSync("templates/interface.d.ts.hb", "utf8"),
53-
{
54-
noEscape: true
55-
}
56-
);
51+
private interfaceTemplate: HandlebarsTemplateDelegate;
5752

58-
enumTemplate: HandlebarsTemplateDelegate<any> = Handlebars.compile(
59-
fs.readFileSync("templates/enums.d.ts.hb", "utf-8"),
60-
{
61-
noEscape: true
62-
}
63-
);
53+
private enumTemplate: HandlebarsTemplateDelegate;
6454

6555
private allClasses: ParsedClass[] = [];
6656
private allNamespaces: ParsedNamespace[] = [];
6757

68-
modularClassTemplate = Handlebars.compile(
69-
fs.readFileSync("templates/classModule.d.ts.hb", "utf8"),
70-
{
71-
noEscape: true
72-
}
73-
);
74-
ambientClassTemplate = Handlebars.compile(
75-
fs.readFileSync("templates/classAmbient.d.ts.hb", "utf8"),
76-
{
77-
noEscape: true
78-
}
79-
);
58+
private modularClassTemplate: HandlebarsTemplateDelegate;
59+
private ambientClassTemplate: HandlebarsTemplateDelegate;
8060

8161
constructor(private configPath: string) {
8262
this.config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
83-
// overwrite setting
8463
this.logger = new Log("Parser", LogLevel.getValue(this.config.logLevel));
8564
this.logger.Trace("Started Logger");
65+
this.ambientClassTemplate = Handlebars.compile(
66+
fs.readFileSync(this.config.templates.ambientClass, "utf8"),
67+
{
68+
noEscape: true
69+
}
70+
);
71+
this.enumTemplate = Handlebars.compile(
72+
fs.readFileSync(this.config.templates.enum, "utf-8"),
73+
{
74+
noEscape: true
75+
}
76+
);
77+
this.modularClassTemplate = Handlebars.compile(
78+
fs.readFileSync(this.config.templates.modularClass, "utf8"),
79+
{
80+
noEscape: true
81+
}
82+
);
83+
this.interfaceTemplate = Handlebars.compile(
84+
fs.readFileSync(this.config.templates.interface, "utf8"),
85+
{
86+
noEscape: true
87+
}
88+
);
8689
}
87-
async GenerateDeclarations(outfolder: string): Promise<void> {
90+
async GenerateDeclarations(outfolder?: string): Promise<void> {
8891
return new Promise<void>(async (resolve, reject) => {
89-
this.outfolder = outfolder;
92+
this.outfolder = outfolder || this.config.outdir;
9093
let info = { generatedClasses: 0 };
9194
// Create rest client
9295
const rc = new RestClient("Agent", this.config.connection.root);
@@ -303,8 +306,7 @@ export class Parser implements ILogDecorator {
303306
try {
304307
this.logger.Debug("Caught Object:");
305308
this.logger.Debug(JSON.stringify(val));
306-
} catch(error) {
307-
}
309+
} catch (error) {}
308310
return func(val);
309311
};
310312
for (const endpointname in apis) {
@@ -316,8 +318,10 @@ export class Parser implements ILogDecorator {
316318
}
317319
bar.tick({ api: endpointname });
318320
} catch (error) {
319-
if(error.message.startsWith("Lexical error")) {
320-
this.logger.Error("Error in used jsonpath. See https://www.npmjs.com/package/jsonpath for how to use jsonpath.");
321+
if (error.message.startsWith("Lexical error")) {
322+
this.logger.Error(
323+
"Error in used jsonpath. See https://www.npmjs.com/package/jsonpath for how to use jsonpath."
324+
);
321325
this.logger.Error("Error Message: " + error.message);
322326
continue;
323327
}
@@ -406,10 +410,7 @@ export class Parser implements ILogDecorator {
406410
type: ISymbol,
407411
info: { generatedEnumCount: number }
408412
): boolean {
409-
if (
410-
type.kind === "enum" ||
411-
(type.kind === "namespace" && this.config.enums[type.name] !== undefined)
412-
) {
413+
if (type.kind === "enum") {
413414
const filepath = path.join(this.outfolder, "enums", type.name + ".d.ts");
414415
if (fs.existsSync(filepath)) {
415416
this.logger.Error("File already exists");
@@ -575,12 +576,17 @@ export class Parser implements ILogDecorator {
575576
private ParseClass(c: ParsedClass): void {
576577
let filepath = path.join(this.outfolder, "classes", c.module + ".d.ts");
577578
if (fs.existsSync(filepath)) {
578-
filepath = path.join(this.outfolder, "classes", c.module + "." + c.basename + ".d.ts");}
579+
filepath = path.join(
580+
this.outfolder,
581+
"classes",
582+
c.module + "." + c.basename + ".d.ts"
583+
);
584+
}
579585
this.log("Creating class " + filepath);
580586
try {
581587
MakeDirRecursiveSync(path.dirname(filepath));
582588
try {
583-
fs.writeFileSync(filepath, c.toString(), { encoding: "utf-8" });
589+
fs.writeFileSync(filepath, c.toString(), { encoding: "utf-8" });
584590
} catch (error) {
585591
console.error(
586592
"Error writing file " + filepath + ": " + error.toString()

0 commit comments

Comments
 (0)