Skip to content

Commit f061f63

Browse files
committed
Normalize acronyms option
1 parent 8adf7e6 commit f061f63

6 files changed

Lines changed: 71 additions & 10 deletions

File tree

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ Controls generated string quotes. Options: `double`, `single`. Default: `double`
8282
`--semicolons`, `--no-semicolons` optional
8383
Controls semicolons on generated TypeScript statements. Options: `true`, `false`. Default: `true`.
8484

85+
`--normalize-acronyms`, `--no-normalize-acronyms` optional
86+
Normalizes leading acronyms in generated property names, such as `ULS` to `uls`. Default: `false`.
87+
8588
## Configuration File
8689

8790
Create `typesharp.json`:
@@ -98,7 +101,8 @@ Create `typesharp.json`:
98101
"dictionaryStyle": "record",
99102
"readonlyProperties": false,
100103
"quoteStyle": "double",
101-
"semicolons": true
104+
"semicolons": true,
105+
"normalizeAcronyms": false
102106
}
103107
```
104108

src/typesharp-ts/dist/main.mjs

Lines changed: 5 additions & 5 deletions
Large diffs are not rendered by default.

src/typesharp-ts/spec/node_test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,33 @@ namespace Demo {
122122
assert.match(generated.get("Demo.Module.ts")!, /export \* from '\.\/Person'/);
123123
});
124124

125+
test("supports default and acronym-normalized property naming", () => {
126+
const files = [
127+
parseSourceFile(
128+
"report.cs",
129+
`
130+
public class ULSReport {}
131+
public class Report {
132+
public string SubCategory { get; set; }
133+
public ULSReport ULS { get; set; }
134+
public string URLValue { get; set; }
135+
}`,
136+
),
137+
];
138+
139+
const defaultReport = new Map(generate(files).map((file) => [file.name, file.text])).get("Report.ts")!;
140+
assert.match(defaultReport, /subCategory: string;/);
141+
assert.match(defaultReport, /uLS: ULSReport;/);
142+
assert.match(defaultReport, /uRLValue: string;/);
143+
144+
const normalizedReport = new Map(
145+
generate(files, { normalizeAcronyms: true }).map((file) => [file.name, file.text]),
146+
).get("Report.ts")!;
147+
assert.match(normalizedReport, /subCategory: string;/);
148+
assert.match(normalizedReport, /uls: ULSReport;/);
149+
assert.match(normalizedReport, /urlValue: string;/);
150+
});
151+
125152
test("supports property readonly attributes without global readonly", () => {
126153
const person = generate([
127154
parseSourceFile(
@@ -225,6 +252,7 @@ test("supports current CLI arguments", () => {
225252
"--readonly-properties",
226253
"--quote-style=single",
227254
"--no-semicolons",
255+
"--normalize-acronyms",
228256
]),
229257
{
230258
configPath: "./config/tssharp.json",
@@ -239,6 +267,7 @@ test("supports current CLI arguments", () => {
239267
readonlyProperties: true,
240268
quoteStyle: "single",
241269
semicolons: false,
270+
normalizeAcronyms: true,
242271
},
243272
);
244273
});
@@ -256,6 +285,7 @@ test("loads typesharp.json and lets CLI override file values", async () => {
256285
exportModule: true,
257286
dictionaryStyle: "index-signature",
258287
readonlyProperties: true,
288+
normalizeAcronyms: true,
259289
}),
260290
);
261291

@@ -272,6 +302,7 @@ test("loads typesharp.json and lets CLI override file values", async () => {
272302
readonlyProperties: true,
273303
quoteStyle: undefined,
274304
semicolons: undefined,
305+
normalizeAcronyms: true,
275306
});
276307
} finally {
277308
await rm(temp, { recursive: true, force: true });
@@ -302,6 +333,7 @@ test("defaults fileFilter to C# source files", async () => {
302333
readonlyProperties: undefined,
303334
quoteStyle: undefined,
304335
semicolons: undefined,
336+
normalizeAcronyms: undefined,
305337
});
306338
} finally {
307339
await rm(temp, { recursive: true, force: true });

src/typesharp-ts/src/generator.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export interface GenerateOptions {
77
readonlyProperties?: boolean;
88
quoteStyle?: "double" | "single";
99
semicolons?: boolean;
10+
normalizeAcronyms?: boolean;
1011
}
1112

1213
export interface GeneratedFile {
@@ -91,7 +92,7 @@ function generateClass(node: ClassNode, typeMap: TypeMap, options: Required<Gene
9192
hasAttribute(prop.attributes, "ReadOnly");
9293
const prefix = readonly ? "readonly " : "";
9394
if (union) {
94-
lines.push(` ${prefix}${camelCase(prop.name)}: ${union}${statementEnd(options)}`);
95+
lines.push(` ${prefix}${propertyName(prop.name, options)}: ${union}${statementEnd(options)}`);
9596
continue;
9697
}
9798

@@ -102,7 +103,9 @@ function generateClass(node: ClassNode, typeMap: TypeMap, options: Required<Gene
102103
: toTypeScriptType(prop.type, node, typeMap, options);
103104
if (hasAttribute(prop.attributes, "Partial")) typeName = `Partial<${typeName}>`;
104105
lines.push(
105-
` ${prefix}${camelCase(prop.name)}${optional ? "?" : ""}: ${typeName}${nullable}${statementEnd(options)}`,
106+
` ${prefix}${propertyName(prop.name, options)}${optional ? "?" : ""}: ${typeName}${nullable}${
107+
statementEnd(options)
108+
}`,
106109
);
107110
}
108111
lines.push("}", "");
@@ -192,11 +195,22 @@ function simpleName(name: string): string {
192195
return name.split(".").at(-1) ?? name;
193196
}
194197

198+
function propertyName(value: string, options: Required<GenerateOptions>): string {
199+
return options.normalizeAcronyms ? acronymCamelCase(value) : camelCase(value);
200+
}
201+
195202
function camelCase(value: string): string {
196203
if (value.length === 0) return value;
197204
return value[0].toLowerCase() + value.slice(1);
198205
}
199206

207+
function acronymCamelCase(value: string): string {
208+
if (value.length === 0) return value;
209+
const leadingAcronym = value.match(/^[A-Z]+(?=$|[A-Z][a-z])/);
210+
if (!leadingAcronym) return camelCase(value);
211+
return leadingAcronym[0].toLowerCase() + value.slice(leadingAcronym[0].length);
212+
}
213+
200214
function normalizeOptions(options: GenerateOptions): Required<GenerateOptions> {
201215
return {
202216
exportModule: options.exportModule ?? false,
@@ -205,6 +219,7 @@ function normalizeOptions(options: GenerateOptions): Required<GenerateOptions> {
205219
readonlyProperties: options.readonlyProperties ?? false,
206220
quoteStyle: options.quoteStyle ?? "double",
207221
semicolons: options.semicolons ?? true,
222+
normalizeAcronyms: options.normalizeAcronyms ?? false,
208223
};
209224
}
210225

src/typesharp-ts/src/main.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export async function convert(options: CliOptions): Promise<void> {
5050
readonlyProperties: options.readonlyProperties,
5151
quoteStyle: options.quoteStyle,
5252
semicolons: options.semicolons,
53+
normalizeAcronyms: options.normalizeAcronyms,
5354
});
5455
await mkdir(options.destination, { recursive: true });
5556
for (const file of files) {
@@ -82,7 +83,13 @@ export function parseArgs(args: string[]): ParsedCliOptions {
8283
w: "watch",
8384
m: "export-module",
8485
};
85-
const booleanFlags = new Set(["watch", "export-module", "readonly-properties", "semicolons"]);
86+
const booleanFlags = new Set([
87+
"watch",
88+
"export-module",
89+
"readonly-properties",
90+
"semicolons",
91+
"normalize-acronyms",
92+
]);
8693
const arrayFlags = new Set(["exclude-pattern", "exclude-patterns"]);
8794

8895
for (let i = 0; i < args.length; i++) {
@@ -157,6 +164,7 @@ function finalizeOptions(options: ConfigFileOptions): CliOptions {
157164
readonlyProperties: options.readonlyProperties,
158165
quoteStyle: options.quoteStyle,
159166
semicolons: options.semicolons,
167+
normalizeAcronyms: options.normalizeAcronyms,
160168
};
161169
}
162170

@@ -174,6 +182,7 @@ function mapRawOptions(values: Map<string, string | boolean>): ParsedCliOptions
174182
setBoolean(options, "readonlyProperties", values.get("readonly-properties"));
175183
setString(options, "quoteStyle", values.get("quote-style"));
176184
setBoolean(options, "semicolons", values.get("semicolons"));
185+
setBoolean(options, "normalizeAcronyms", values.get("normalize-acronyms"));
177186
return options;
178187
}
179188

src/typesharp-ts/typesharp.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@
99
"dictionaryStyle": "record",
1010
"readonlyProperties": false,
1111
"quoteStyle": "double",
12-
"semicolons": true
12+
"semicolons": true,
13+
"normalizeAcronyms": false
1314
}

0 commit comments

Comments
 (0)