dto2ts 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +254 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +59 -0
- package/dist/config.d.ts +24 -0
- package/dist/config.js +68 -0
- package/dist/generate.d.ts +10 -0
- package/dist/generate.js +87 -0
- package/dist/generators/http-client.d.ts +5 -0
- package/dist/generators/http-client.js +133 -0
- package/dist/generators/models.d.ts +10 -0
- package/dist/generators/models.js +77 -0
- package/dist/generators/services.d.ts +6 -0
- package/dist/generators/services.js +77 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +15 -0
- package/dist/ir.d.ts +48 -0
- package/dist/ir.js +8 -0
- package/dist/parsers/csharp-types.d.ts +9 -0
- package/dist/parsers/csharp-types.js +111 -0
- package/dist/parsers/csharp.d.ts +3 -0
- package/dist/parsers/csharp.js +224 -0
- package/dist/parsers/openapi.d.ts +3 -0
- package/dist/parsers/openapi.js +218 -0
- package/dist/parsers/resources.d.ts +10 -0
- package/dist/parsers/resources.js +41 -0
- package/dist/utils/fs.d.ts +4 -0
- package/dist/utils/fs.js +69 -0
- package/dist/utils/naming.d.ts +10 -0
- package/dist/utils/naming.js +42 -0
- package/package.json +43 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.modelFileBase = modelFileBase;
|
|
4
|
+
exports.generateModelFiles = generateModelFiles;
|
|
5
|
+
const naming_1 = require("../utils/naming");
|
|
6
|
+
const HEADER = "// Bu dosya dto2ts tarafından otomatik üretildi. Elle düzenlemeyin.\n";
|
|
7
|
+
function isValidIdentifier(name) {
|
|
8
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name);
|
|
9
|
+
}
|
|
10
|
+
function propKey(name) {
|
|
11
|
+
return isValidIdentifier(name) ? name : JSON.stringify(name);
|
|
12
|
+
}
|
|
13
|
+
/** Bir modelin uzantısız dosya adı tabanı. */
|
|
14
|
+
function modelFileBase(m) {
|
|
15
|
+
const kebab = (0, naming_1.toKebabCase)(m.name);
|
|
16
|
+
return m.kind === "enum" ? `${kebab}.enum` : `${kebab}.model`;
|
|
17
|
+
}
|
|
18
|
+
function emitEnum(model) {
|
|
19
|
+
const lines = model.members.map((m) => {
|
|
20
|
+
const value = typeof m.value === "number" ? String(m.value) : JSON.stringify(m.value);
|
|
21
|
+
const key = isValidIdentifier(m.name) ? m.name : JSON.stringify(m.name);
|
|
22
|
+
return ` ${key} = ${value},`;
|
|
23
|
+
});
|
|
24
|
+
return `export enum ${model.name} {\n${lines.join("\n")}\n}\n`;
|
|
25
|
+
}
|
|
26
|
+
function emitInterface(model, knownInterfaces) {
|
|
27
|
+
const ext = (model.extends ?? []).filter((e) => knownInterfaces.has(e));
|
|
28
|
+
const extendsClause = ext.length ? ` extends ${ext.join(", ")}` : "";
|
|
29
|
+
const body = model.properties
|
|
30
|
+
.map((p) => {
|
|
31
|
+
const doc = p.description ? ` /** ${p.description.replace(/\*\//g, "*\\/")} */\n` : "";
|
|
32
|
+
const opt = p.optional ? "?" : "";
|
|
33
|
+
return `${doc} ${propKey(p.name)}${opt}: ${p.type};`;
|
|
34
|
+
})
|
|
35
|
+
.join("\n");
|
|
36
|
+
const doc = model.description ? `/** ${model.description.replace(/\*\//g, "*\\/")} */\n` : "";
|
|
37
|
+
return `${doc}export interface ${model.name}${extendsClause} {\n${body}\n}\n`;
|
|
38
|
+
}
|
|
39
|
+
/** Bir modelin, TS tiplerinde referans verdiği diğer model adlarını bul. */
|
|
40
|
+
function referencedNames(model, allNames) {
|
|
41
|
+
const refs = new Set();
|
|
42
|
+
for (const e of model.extends ?? [])
|
|
43
|
+
if (allNames.has(e))
|
|
44
|
+
refs.add(e);
|
|
45
|
+
for (const p of model.properties) {
|
|
46
|
+
for (const name of allNames) {
|
|
47
|
+
if (name === model.name)
|
|
48
|
+
continue;
|
|
49
|
+
if (new RegExp(`\\b${name}\\b`).test(p.type))
|
|
50
|
+
refs.add(name);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return refs;
|
|
54
|
+
}
|
|
55
|
+
/** Her modeli kendi dosyasına derle (models/<ad>.model.ts | <ad>.enum.ts) + barrel. */
|
|
56
|
+
function generateModelFiles(api) {
|
|
57
|
+
const knownInterfaces = new Set(api.models.filter((m) => m.kind === "interface").map((m) => m.name));
|
|
58
|
+
const allNames = new Set(api.models.map((m) => m.name));
|
|
59
|
+
const byName = new Map(api.models.map((m) => [m.name, m]));
|
|
60
|
+
const files = [];
|
|
61
|
+
for (const model of api.models) {
|
|
62
|
+
const refs = [...referencedNames(model, allNames)].sort();
|
|
63
|
+
const importLines = refs
|
|
64
|
+
.map((r) => `import type { ${r} } from "./${modelFileBase(byName.get(r))}";`)
|
|
65
|
+
.join("\n");
|
|
66
|
+
const decl = model.kind === "enum" ? emitEnum(model) : emitInterface(model, knownInterfaces);
|
|
67
|
+
const content = HEADER + (importLines ? "\n" + importLines + "\n" : "") + "\n" + decl;
|
|
68
|
+
files.push({ path: `models/${modelFileBase(model)}.ts`, content });
|
|
69
|
+
}
|
|
70
|
+
// models/index.ts barrel
|
|
71
|
+
const barrel = HEADER +
|
|
72
|
+
"\n" +
|
|
73
|
+
api.models.map((m) => `export * from "./${modelFileBase(m)}";`).join("\n") +
|
|
74
|
+
"\n";
|
|
75
|
+
files.push({ path: "models/index.ts", content: barrel });
|
|
76
|
+
return files;
|
|
77
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ApiModel, ResourceDef } from "../ir";
|
|
2
|
+
import { GeneratedFile } from "./models";
|
|
3
|
+
/** Bir kaynağın uzantısız service dosya adı tabanı. Örn. "product.service". */
|
|
4
|
+
export declare function serviceFileBase(r: ResourceDef): string;
|
|
5
|
+
/** Her kaynak için service dosyası (services/<ad>.service.ts) + barrel. */
|
|
6
|
+
export declare function generateServiceFiles(api: ApiModel): GeneratedFile[];
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.serviceFileBase = serviceFileBase;
|
|
4
|
+
exports.generateServiceFiles = generateServiceFiles;
|
|
5
|
+
const naming_1 = require("../utils/naming");
|
|
6
|
+
const HEADER = "// Bu dosya dto2ts tarafından otomatik üretildi. Elle düzenlemeyin.\n";
|
|
7
|
+
/** Bir kaynağın uzantısız service dosya adı tabanı. Örn. "product.service". */
|
|
8
|
+
function serviceFileBase(r) {
|
|
9
|
+
return `${(0, naming_1.toKebabCase)(r.name)}.service`;
|
|
10
|
+
}
|
|
11
|
+
function emitService(r) {
|
|
12
|
+
const cls = `${r.name}Service`;
|
|
13
|
+
const model = r.model;
|
|
14
|
+
const idT = r.idType;
|
|
15
|
+
return `/**
|
|
16
|
+
* ${r.name} kaynağı için CRUD service.
|
|
17
|
+
* Base path: ${r.basePath}
|
|
18
|
+
*/
|
|
19
|
+
export class ${cls} {
|
|
20
|
+
constructor(
|
|
21
|
+
private readonly http: HttpClient,
|
|
22
|
+
private readonly basePath: string = ${JSON.stringify(r.basePath)}
|
|
23
|
+
) {}
|
|
24
|
+
|
|
25
|
+
/** Tüm kayıtları getir. */
|
|
26
|
+
list(options?: RequestOptions): Promise<${model}[]> {
|
|
27
|
+
return this.http.get<${model}[]>(this.basePath, options);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Id ile tek kayıt getir. */
|
|
31
|
+
getById(id: ${idT}, options?: RequestOptions): Promise<${model}> {
|
|
32
|
+
return this.http.get<${model}>(\`\${this.basePath}/\${id}\`, options);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Yeni kayıt oluştur. */
|
|
36
|
+
create(payload: ${model}, options?: RequestOptions): Promise<${model}> {
|
|
37
|
+
return this.http.post<${model}>(this.basePath, payload, options);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Var olan kaydı tümüyle güncelle. */
|
|
41
|
+
update(id: ${idT}, payload: ${model}, options?: RequestOptions): Promise<${model}> {
|
|
42
|
+
return this.http.put<${model}>(\`\${this.basePath}/\${id}\`, payload, options);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Kaydı kısmi güncelle. */
|
|
46
|
+
patch(id: ${idT}, payload: Partial<${model}>, options?: RequestOptions): Promise<${model}> {
|
|
47
|
+
return this.http.patch<${model}>(\`\${this.basePath}/\${id}\`, payload, options);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Kaydı sil. */
|
|
51
|
+
remove(id: ${idT}, options?: RequestOptions): Promise<void> {
|
|
52
|
+
return this.http.delete<void>(\`\${this.basePath}/\${id}\`, options);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
`;
|
|
56
|
+
}
|
|
57
|
+
/** Her kaynak için service dosyası (services/<ad>.service.ts) + barrel. */
|
|
58
|
+
function generateServiceFiles(api) {
|
|
59
|
+
if (!api.resources.length)
|
|
60
|
+
return [];
|
|
61
|
+
const files = [];
|
|
62
|
+
for (const r of api.resources) {
|
|
63
|
+
const content = HEADER +
|
|
64
|
+
"\n" +
|
|
65
|
+
`import { HttpClient } from "../http-client";\n` +
|
|
66
|
+
`import type { RequestOptions } from "../http-client";\n` +
|
|
67
|
+
`import type { ${r.model} } from "../models";\n\n` +
|
|
68
|
+
emitService(r);
|
|
69
|
+
files.push({ path: `services/${serviceFileBase(r)}.ts`, content });
|
|
70
|
+
}
|
|
71
|
+
const barrel = HEADER +
|
|
72
|
+
"\n" +
|
|
73
|
+
api.resources.map((r) => `export * from "./${serviceFileBase(r)}";`).join("\n") +
|
|
74
|
+
"\n";
|
|
75
|
+
files.push({ path: "services/index.ts", content: barrel });
|
|
76
|
+
return files;
|
|
77
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dto2ts — programatik API.
|
|
3
|
+
*
|
|
4
|
+
* CLI için `dto2ts` binary'sini kullanın; kodun içinden çağırmak için `generate`.
|
|
5
|
+
*/
|
|
6
|
+
export { generate, parseSource } from "./generate";
|
|
7
|
+
export type { GenerateResult } from "./generate";
|
|
8
|
+
export { GenConfig, DEFAULT_CONFIG, loadConfigFile, resolveConfig, } from "./config";
|
|
9
|
+
export type { ApiModel, ModelDef, PropertyDef, EnumMemberDef, ResourceDef, } from "./ir";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveConfig = exports.loadConfigFile = exports.DEFAULT_CONFIG = exports.parseSource = exports.generate = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* dto2ts — programatik API.
|
|
6
|
+
*
|
|
7
|
+
* CLI için `dto2ts` binary'sini kullanın; kodun içinden çağırmak için `generate`.
|
|
8
|
+
*/
|
|
9
|
+
var generate_1 = require("./generate");
|
|
10
|
+
Object.defineProperty(exports, "generate", { enumerable: true, get: function () { return generate_1.generate; } });
|
|
11
|
+
Object.defineProperty(exports, "parseSource", { enumerable: true, get: function () { return generate_1.parseSource; } });
|
|
12
|
+
var config_1 = require("./config");
|
|
13
|
+
Object.defineProperty(exports, "DEFAULT_CONFIG", { enumerable: true, get: function () { return config_1.DEFAULT_CONFIG; } });
|
|
14
|
+
Object.defineProperty(exports, "loadConfigFile", { enumerable: true, get: function () { return config_1.loadConfigFile; } });
|
|
15
|
+
Object.defineProperty(exports, "resolveConfig", { enumerable: true, get: function () { return config_1.resolveConfig; } });
|
package/dist/ir.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ara model (Intermediate Representation).
|
|
3
|
+
*
|
|
4
|
+
* Hem OpenAPI parser'ı hem C# parser'ı çıktı olarak bu yapıyı üretir.
|
|
5
|
+
* Generator'lar yalnızca bu yapıya bakar; böylece kaynak formatından bağımsızdır.
|
|
6
|
+
*/
|
|
7
|
+
/** Tek bir modelin (interface veya enum) tanımı. */
|
|
8
|
+
export interface ModelDef {
|
|
9
|
+
/** TS tip adı (PascalCase). Örn. "ProductDto". */
|
|
10
|
+
name: string;
|
|
11
|
+
kind: "interface" | "enum";
|
|
12
|
+
description?: string;
|
|
13
|
+
properties: PropertyDef[];
|
|
14
|
+
/** Kalıtım: bu model başka bir modeli genişletiyorsa (extends). */
|
|
15
|
+
extends?: string[];
|
|
16
|
+
members: EnumMemberDef[];
|
|
17
|
+
}
|
|
18
|
+
export interface PropertyDef {
|
|
19
|
+
/** TS/JSON içindeki alan adı (varsayılan camelCase). */
|
|
20
|
+
name: string;
|
|
21
|
+
/** Kaynaktaki orijinal ad (C#'ta PascalCase olabilir). */
|
|
22
|
+
originalName: string;
|
|
23
|
+
/** Çözülmüş TS tip ifadesi. Örn. "string", "Foo[]", "Record<string, number>". */
|
|
24
|
+
type: string;
|
|
25
|
+
/** null/undefined olabilir mi (opsiyonel alan). */
|
|
26
|
+
optional: boolean;
|
|
27
|
+
description?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface EnumMemberDef {
|
|
30
|
+
name: string;
|
|
31
|
+
value: string | number;
|
|
32
|
+
}
|
|
33
|
+
/** Bir CRUD kaynağı (entity) için service üretim bilgisi. */
|
|
34
|
+
export interface ResourceDef {
|
|
35
|
+
/** Service'in dayandığı model adı. Örn. "ProductDto". */
|
|
36
|
+
model: string;
|
|
37
|
+
/** Service sınıf adı için kök. Örn. "Product". */
|
|
38
|
+
name: string;
|
|
39
|
+
/** API base path. Örn. "/api/products". */
|
|
40
|
+
basePath: string;
|
|
41
|
+
/** Id alanının TS tipi. Örn. "number" | "string". */
|
|
42
|
+
idType: string;
|
|
43
|
+
}
|
|
44
|
+
/** Parser'ların ürettiği, generator'ların tükettiği tam model. */
|
|
45
|
+
export interface ApiModel {
|
|
46
|
+
models: ModelDef[];
|
|
47
|
+
resources: ResourceDef[];
|
|
48
|
+
}
|
package/dist/ir.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Ara model (Intermediate Representation).
|
|
4
|
+
*
|
|
5
|
+
* Hem OpenAPI parser'ı hem C# parser'ı çıktı olarak bu yapıyı üretir.
|
|
6
|
+
* Generator'lar yalnızca bu yapıya bakar; böylece kaynak formatından bağımsızdır.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { GenConfig } from "../config";
|
|
2
|
+
export interface MappedType {
|
|
3
|
+
/** TS tip ifadesi. */
|
|
4
|
+
tsType: string;
|
|
5
|
+
/** C# nullable işareti (Type? / Nullable<Type>) bulundu mu. */
|
|
6
|
+
nullable: boolean;
|
|
7
|
+
}
|
|
8
|
+
/** Bir C# tip ifadesini TS'ye çevir. */
|
|
9
|
+
export declare function mapCsharpType(rawInput: string, config: GenConfig): MappedType;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mapCsharpType = mapCsharpType;
|
|
4
|
+
const PRIMITIVES = {
|
|
5
|
+
string: "string",
|
|
6
|
+
char: "string",
|
|
7
|
+
guid: "string",
|
|
8
|
+
bool: "boolean",
|
|
9
|
+
boolean: "boolean",
|
|
10
|
+
byte: "number",
|
|
11
|
+
sbyte: "number",
|
|
12
|
+
short: "number",
|
|
13
|
+
ushort: "number",
|
|
14
|
+
int: "number",
|
|
15
|
+
int16: "number",
|
|
16
|
+
int32: "number",
|
|
17
|
+
int64: "number",
|
|
18
|
+
uint: "number",
|
|
19
|
+
long: "number",
|
|
20
|
+
ulong: "number",
|
|
21
|
+
decimal: "number",
|
|
22
|
+
double: "number",
|
|
23
|
+
float: "number",
|
|
24
|
+
single: "number",
|
|
25
|
+
object: "unknown",
|
|
26
|
+
dynamic: "unknown",
|
|
27
|
+
};
|
|
28
|
+
function splitGenericArgs(inner) {
|
|
29
|
+
// "string, List<int>" -> ["string", "List<int>"] (iç içe generic'e dikkat).
|
|
30
|
+
const args = [];
|
|
31
|
+
let depth = 0;
|
|
32
|
+
let current = "";
|
|
33
|
+
for (const ch of inner) {
|
|
34
|
+
if (ch === "<")
|
|
35
|
+
depth++;
|
|
36
|
+
if (ch === ">")
|
|
37
|
+
depth--;
|
|
38
|
+
if (ch === "," && depth === 0) {
|
|
39
|
+
args.push(current.trim());
|
|
40
|
+
current = "";
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
current += ch;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (current.trim())
|
|
47
|
+
args.push(current.trim());
|
|
48
|
+
return args;
|
|
49
|
+
}
|
|
50
|
+
/** Bir C# tip ifadesini TS'ye çevir. */
|
|
51
|
+
function mapCsharpType(rawInput, config) {
|
|
52
|
+
let raw = rawInput.trim();
|
|
53
|
+
let nullable = false;
|
|
54
|
+
// Task<T> / ValueTask<T> sar-çöz
|
|
55
|
+
const taskMatch = /^(?:Task|ValueTask)<(.+)>$/.exec(raw);
|
|
56
|
+
if (taskMatch)
|
|
57
|
+
return mapCsharpType(taskMatch[1], config);
|
|
58
|
+
// Nullable<T>
|
|
59
|
+
const nullableGeneric = /^Nullable<(.+)>$/.exec(raw);
|
|
60
|
+
if (nullableGeneric) {
|
|
61
|
+
const inner = mapCsharpType(nullableGeneric[1], config);
|
|
62
|
+
return { tsType: inner.tsType, nullable: true };
|
|
63
|
+
}
|
|
64
|
+
// Trailing "?" (int?, string?)
|
|
65
|
+
if (raw.endsWith("?")) {
|
|
66
|
+
nullable = true;
|
|
67
|
+
raw = raw.slice(0, -1).trim();
|
|
68
|
+
}
|
|
69
|
+
// Diziler: T[]
|
|
70
|
+
if (raw.endsWith("[]")) {
|
|
71
|
+
const inner = mapCsharpType(raw.slice(0, -2), config);
|
|
72
|
+
return { tsType: `${inner.tsType}[]`, nullable };
|
|
73
|
+
}
|
|
74
|
+
// Generic koleksiyonlar / sözlük
|
|
75
|
+
const generic = /^([A-Za-z0-9_.]+)<(.+)>$/.exec(raw);
|
|
76
|
+
if (generic) {
|
|
77
|
+
const container = generic[1].split(".").pop(); // namespace at
|
|
78
|
+
const args = splitGenericArgs(generic[2]);
|
|
79
|
+
const listLike = ["List", "IList", "IEnumerable", "ICollection", "IReadOnlyList",
|
|
80
|
+
"IReadOnlyCollection", "Collection", "HashSet", "ISet", "Queue", "Stack"];
|
|
81
|
+
if (listLike.includes(container) && args.length === 1) {
|
|
82
|
+
const inner = mapCsharpType(args[0], config);
|
|
83
|
+
return { tsType: `${inner.tsType}[]`, nullable };
|
|
84
|
+
}
|
|
85
|
+
const dictLike = ["Dictionary", "IDictionary", "IReadOnlyDictionary", "SortedDictionary"];
|
|
86
|
+
if (dictLike.includes(container) && args.length === 2) {
|
|
87
|
+
const key = mapCsharpType(args[0], config).tsType;
|
|
88
|
+
const val = mapCsharpType(args[1], config).tsType;
|
|
89
|
+
const keyType = key === "number" ? "number" : "string";
|
|
90
|
+
return { tsType: `Record<${keyType}, ${val}>`, nullable };
|
|
91
|
+
}
|
|
92
|
+
// Bilinmeyen generic: sadece kök adı kullan (custom tip varsayımı).
|
|
93
|
+
return { tsType: container, nullable };
|
|
94
|
+
}
|
|
95
|
+
// Namespace'i at (System.String -> String)
|
|
96
|
+
const shortName = raw.split(".").pop();
|
|
97
|
+
const key = shortName.toLowerCase();
|
|
98
|
+
if (key in PRIMITIVES) {
|
|
99
|
+
// byte[] zaten yukarıda; primitive
|
|
100
|
+
return { tsType: PRIMITIVES[key], nullable };
|
|
101
|
+
}
|
|
102
|
+
// Tarih/zaman tipleri
|
|
103
|
+
if (["datetime", "datetimeoffset", "dateonly", "timeonly", "timespan"].includes(key)) {
|
|
104
|
+
return { tsType: config.dateAsString ? "string" : "Date", nullable };
|
|
105
|
+
}
|
|
106
|
+
// Referans tipleri (string, custom class) TS'de null olabilir ama C# non-nullable ref
|
|
107
|
+
// olarak işaretlenmemişse çoğu API camelCase JSON'da alan gönderir; optional'ı
|
|
108
|
+
// yalnızca açık nullable işaretinden belirliyoruz.
|
|
109
|
+
// Aksi halde custom tip: adını olduğu gibi bırak (PascalCase model referansı).
|
|
110
|
+
return { tsType: shortName, nullable };
|
|
111
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.parseCsharp = parseCsharp;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const fs_1 = require("../utils/fs");
|
|
39
|
+
const naming_1 = require("../utils/naming");
|
|
40
|
+
const csharp_types_1 = require("./csharp-types");
|
|
41
|
+
const resources_1 = require("./resources");
|
|
42
|
+
/** Yorumları temizle (satır ve blok). String literalleri kabaca korur. */
|
|
43
|
+
function stripComments(src) {
|
|
44
|
+
return src
|
|
45
|
+
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
46
|
+
.replace(/\/\/[^\n]*/g, "");
|
|
47
|
+
}
|
|
48
|
+
/** index'teki açılış `{` için eşleşen kapanış `}` indeksini bul. */
|
|
49
|
+
function matchBrace(src, openIndex) {
|
|
50
|
+
let depth = 0;
|
|
51
|
+
for (let i = openIndex; i < src.length; i++) {
|
|
52
|
+
if (src[i] === "{")
|
|
53
|
+
depth++;
|
|
54
|
+
else if (src[i] === "}") {
|
|
55
|
+
depth--;
|
|
56
|
+
if (depth === 0)
|
|
57
|
+
return i;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return -1;
|
|
61
|
+
}
|
|
62
|
+
function parseEnumBody(name, body) {
|
|
63
|
+
const members = [];
|
|
64
|
+
let auto = 0;
|
|
65
|
+
for (const rawMember of body.split(",")) {
|
|
66
|
+
const member = rawMember.trim();
|
|
67
|
+
if (!member)
|
|
68
|
+
continue;
|
|
69
|
+
const eq = member.split("=");
|
|
70
|
+
const memberName = eq[0].trim();
|
|
71
|
+
if (!memberName)
|
|
72
|
+
continue;
|
|
73
|
+
if (eq.length > 1) {
|
|
74
|
+
const rawVal = eq[1].trim();
|
|
75
|
+
const num = Number(rawVal);
|
|
76
|
+
if (!Number.isNaN(num)) {
|
|
77
|
+
members.push({ name: memberName, value: num });
|
|
78
|
+
auto = num + 1;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
members.push({ name: memberName, value: rawVal });
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
members.push({ name: memberName, value: auto });
|
|
85
|
+
auto++;
|
|
86
|
+
}
|
|
87
|
+
return { name, kind: "enum", properties: [], members };
|
|
88
|
+
}
|
|
89
|
+
const PROP_RE = /public\s+(?:virtual\s+|override\s+|required\s+|static\s+|readonly\s+)*([A-Za-z0-9_.<>,\[\]?\s]+?)\s+([A-Za-z_][A-Za-z0-9_]*)\s*\{\s*get\b/g;
|
|
90
|
+
function parseClassBody(name, extendsList, body, config) {
|
|
91
|
+
const properties = [];
|
|
92
|
+
PROP_RE.lastIndex = 0;
|
|
93
|
+
let m;
|
|
94
|
+
while ((m = PROP_RE.exec(body)) !== null) {
|
|
95
|
+
const rawType = m[1].trim();
|
|
96
|
+
const propName = m[2];
|
|
97
|
+
const mapped = (0, csharp_types_1.mapCsharpType)(rawType, config);
|
|
98
|
+
properties.push({
|
|
99
|
+
name: config.camelCase ? (0, naming_1.toCamelCase)(propName) : propName,
|
|
100
|
+
originalName: propName,
|
|
101
|
+
type: mapped.tsType,
|
|
102
|
+
optional: mapped.nullable,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
return { name, kind: "interface", properties, members: [], extends: extendsList };
|
|
106
|
+
}
|
|
107
|
+
/** Positional record: record PersonDto(string Name, int Age); */
|
|
108
|
+
function parseRecordParams(name, extendsList, params, config) {
|
|
109
|
+
const properties = [];
|
|
110
|
+
// Parametreleri virgülden ayır (generic'e dikkat).
|
|
111
|
+
let depth = 0;
|
|
112
|
+
let current = "";
|
|
113
|
+
const parts = [];
|
|
114
|
+
for (const ch of params) {
|
|
115
|
+
if (ch === "<" || ch === "(")
|
|
116
|
+
depth++;
|
|
117
|
+
else if (ch === ">" || ch === ")")
|
|
118
|
+
depth--;
|
|
119
|
+
if (ch === "," && depth === 0) {
|
|
120
|
+
parts.push(current);
|
|
121
|
+
current = "";
|
|
122
|
+
}
|
|
123
|
+
else
|
|
124
|
+
current += ch;
|
|
125
|
+
}
|
|
126
|
+
if (current.trim())
|
|
127
|
+
parts.push(current);
|
|
128
|
+
for (const part of parts) {
|
|
129
|
+
const tokens = part.trim().split(/\s+/);
|
|
130
|
+
if (tokens.length < 2)
|
|
131
|
+
continue;
|
|
132
|
+
const propName = tokens[tokens.length - 1];
|
|
133
|
+
const rawType = tokens.slice(0, -1).join(" ");
|
|
134
|
+
const mapped = (0, csharp_types_1.mapCsharpType)(rawType, config);
|
|
135
|
+
properties.push({
|
|
136
|
+
name: config.camelCase ? (0, naming_1.toCamelCase)(propName) : propName,
|
|
137
|
+
originalName: propName,
|
|
138
|
+
type: mapped.tsType,
|
|
139
|
+
optional: mapped.nullable,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
return { name, kind: "interface", properties, members: [], extends: extendsList };
|
|
143
|
+
}
|
|
144
|
+
function parseInheritance(clause) {
|
|
145
|
+
if (!clause)
|
|
146
|
+
return [];
|
|
147
|
+
return clause
|
|
148
|
+
.split(",")
|
|
149
|
+
.map((s) => s.trim().split("<")[0].trim()) // generic argümanları at
|
|
150
|
+
.filter((s) => /^[A-Z][A-Za-z0-9_]*$/.test(s)); // interface (IFoo) gibi olanları da bırak; generator filtreler
|
|
151
|
+
}
|
|
152
|
+
function parseSource(src, config, models) {
|
|
153
|
+
const clean = stripComments(src);
|
|
154
|
+
// Enumlar
|
|
155
|
+
const enumRe = /\benum\s+([A-Za-z_][A-Za-z0-9_]*)\s*(?::\s*[A-Za-z0-9_.]+)?\s*\{/g;
|
|
156
|
+
let em;
|
|
157
|
+
while ((em = enumRe.exec(clean)) !== null) {
|
|
158
|
+
const open = clean.indexOf("{", em.index);
|
|
159
|
+
const close = matchBrace(clean, open);
|
|
160
|
+
if (close === -1)
|
|
161
|
+
continue;
|
|
162
|
+
const body = clean.slice(open + 1, close);
|
|
163
|
+
models.push(parseEnumBody(em[1], body));
|
|
164
|
+
}
|
|
165
|
+
// class / struct / record (gövdeli)
|
|
166
|
+
const typeRe = /\b(?:public\s+|internal\s+|abstract\s+|sealed\s+|partial\s+)*\b(class|struct|record(?:\s+class|\s+struct)?)\s+([A-Za-z_][A-Za-z0-9_]*)\s*(?:<[^>{(]+>)?\s*(?:\(([^)]*)\))?\s*(?::\s*([^{;]+?))?\s*(\{|;)/g;
|
|
167
|
+
let tm;
|
|
168
|
+
while ((tm = typeRe.exec(clean)) !== null) {
|
|
169
|
+
const typeName = tm[2];
|
|
170
|
+
const recordParams = tm[3];
|
|
171
|
+
const inheritance = parseInheritance(tm[4]);
|
|
172
|
+
const terminator = tm[5];
|
|
173
|
+
if (terminator === ";") {
|
|
174
|
+
// Gövdesiz positional record: record Foo(...);
|
|
175
|
+
if (recordParams !== undefined) {
|
|
176
|
+
models.push(parseRecordParams(typeName, inheritance, recordParams, config));
|
|
177
|
+
}
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
const open = clean.indexOf("{", tm.index);
|
|
181
|
+
const close = matchBrace(clean, open);
|
|
182
|
+
if (close === -1)
|
|
183
|
+
continue;
|
|
184
|
+
const body = clean.slice(open + 1, close);
|
|
185
|
+
const classModel = parseClassBody(typeName, inheritance, body, config);
|
|
186
|
+
// Positional record hem parametre hem gövde içerebilir -> birleştir.
|
|
187
|
+
if (recordParams !== undefined) {
|
|
188
|
+
const posModel = parseRecordParams(typeName, inheritance, recordParams, config);
|
|
189
|
+
const seen = new Set(classModel.properties.map((p) => p.originalName));
|
|
190
|
+
for (const p of posModel.properties) {
|
|
191
|
+
if (!seen.has(p.originalName))
|
|
192
|
+
classModel.properties.push(p);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
models.push(classModel);
|
|
196
|
+
// Sonraki aramayı gövdeden sonra sürdür (iç içe tipleri atlar).
|
|
197
|
+
typeRe.lastIndex = close;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
function parseCsharp(config) {
|
|
201
|
+
const files = (0, fs_1.collectCsFiles)(config.input);
|
|
202
|
+
const models = [];
|
|
203
|
+
for (const file of files) {
|
|
204
|
+
const src = fs.readFileSync(file, "utf8");
|
|
205
|
+
parseSource(src, config, models);
|
|
206
|
+
}
|
|
207
|
+
// Aynı isimli modelleri tekilleştir (partial class'lar vb.).
|
|
208
|
+
const byName = new Map();
|
|
209
|
+
for (const model of models) {
|
|
210
|
+
const existing = byName.get(model.name);
|
|
211
|
+
if (!existing) {
|
|
212
|
+
byName.set(model.name, model);
|
|
213
|
+
}
|
|
214
|
+
else if (model.kind === "interface" && existing.kind === "interface") {
|
|
215
|
+
const seen = new Set(existing.properties.map((p) => p.originalName));
|
|
216
|
+
for (const p of model.properties) {
|
|
217
|
+
if (!seen.has(p.originalName))
|
|
218
|
+
existing.properties.push(p);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
const deduped = [...byName.values()];
|
|
223
|
+
return { models: deduped, resources: (0, resources_1.buildResources)(deduped, config) };
|
|
224
|
+
}
|