Skip to content

Commit 9c7fcfa

Browse files
committed
feat: improve with reservedWords
1 parent eabc989 commit 9c7fcfa

File tree

19 files changed

+278
-82
lines changed

19 files changed

+278
-82
lines changed

docs/generators/typescript-nestjs.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,11 @@ These options may be applied as additional-properties (cli) or configOptions (pl
116116
<li>float</li>
117117
<li>for</li>
118118
<li>formParams</li>
119+
<li>from</li>
119120
<li>function</li>
120121
<li>goto</li>
121122
<li>headerParams</li>
123+
<li>headers</li>
122124
<li>if</li>
123125
<li>implements</li>
124126
<li>import</li>

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptNestjsClientCodegen.java

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package org.openapitools.codegen.languages;
1818

1919
import io.swagger.v3.oas.models.media.Schema;
20+
import io.swagger.v3.oas.models.parameters.Parameter;
21+
import io.swagger.v3.oas.models.parameters.RequestBody;
2022
import lombok.Getter;
2123
import lombok.Setter;
2224
import org.openapitools.codegen.*;
@@ -88,6 +90,8 @@ public TypeScriptNestjsClientCodegen() {
8890
apiPackage = "api";
8991
modelPackage = "model";
9092

93+
reservedWords.addAll(Arrays.asList("from", "headers"));
94+
9195
this.cliOptions.add(new CliOption(NPM_REPOSITORY,
9296
"Use this property to set an url your private npmRepo in the package.json"));
9397
this.cliOptions.add(CliOption.newBoolean(WITH_INTERFACES,
@@ -265,6 +269,34 @@ private boolean isLanguageGenericType(String type) {
265269
return false;
266270
}
267271

272+
@Override
273+
public List<CodegenParameter> fromRequestBodyToFormParameters(RequestBody body, Set<String> imports) {
274+
List<CodegenParameter> superParams = super.fromRequestBodyToFormParameters(body, imports);
275+
List<CodegenParameter> extendedParams = new ArrayList<CodegenParameter>();
276+
for (CodegenParameter cp : superParams) {
277+
extendedParams.add(new ExtendedCodegenParameter(cp));
278+
}
279+
return extendedParams;
280+
}
281+
282+
@Override
283+
public ExtendedCodegenParameter fromParameter(Parameter parameter, Set<String> imports) {
284+
CodegenParameter cp = super.fromParameter(parameter, imports);
285+
return new ExtendedCodegenParameter(cp);
286+
}
287+
288+
@Override
289+
public CodegenParameter fromFormProperty(String name, Schema propertySchema, Set<String> imports) {
290+
CodegenParameter cp = super.fromFormProperty(name, propertySchema, imports);
291+
return new ExtendedCodegenParameter(cp);
292+
}
293+
294+
@Override
295+
public CodegenParameter fromRequestBody(RequestBody body, Set<String> imports, String bodyParameterName) {
296+
CodegenParameter cp = super.fromRequestBody(body, imports, bodyParameterName);
297+
return new ExtendedCodegenParameter(cp);
298+
}
299+
268300
@Override
269301
public void postProcessParameter(CodegenParameter parameter) {
270302
super.postProcessParameter(parameter);
@@ -327,6 +359,12 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap operations, L
327359

328360
// Overwrite path to TypeScript template string, after applying everything we just did.
329361
op.path = pathBuffer.toString();
362+
363+
for (CodegenParameter cpParam : op.allParams) {
364+
ExtendedCodegenParameter param = (ExtendedCodegenParameter) cpParam;
365+
366+
param.hasSanitizedName = !param.baseName.equals(param.paramName);
367+
}
330368
}
331369

332370
operations.put("hasSomeFormParams", hasSomeFormParams);
@@ -482,6 +520,126 @@ public String removeModelPrefixSuffix(String name) {
482520
return result;
483521
}
484522

523+
public class ExtendedCodegenParameter extends CodegenParameter {
524+
public boolean hasSanitizedName = false;
525+
526+
public ExtendedCodegenParameter(CodegenParameter cp) {
527+
super();
528+
529+
this.isFormParam = cp.isFormParam;
530+
this.isQueryParam = cp.isQueryParam;
531+
this.isPathParam = cp.isPathParam;
532+
this.isHeaderParam = cp.isHeaderParam;
533+
this.isCookieParam = cp.isCookieParam;
534+
this.isBodyParam = cp.isBodyParam;
535+
this.isContainer = cp.isContainer;
536+
this.isCollectionFormatMulti = cp.isCollectionFormatMulti;
537+
this.isPrimitiveType = cp.isPrimitiveType;
538+
this.isModel = cp.isModel;
539+
this.isExplode = cp.isExplode;
540+
this.baseName = cp.baseName;
541+
this.paramName = cp.paramName;
542+
this.dataType = cp.dataType;
543+
this.datatypeWithEnum = cp.datatypeWithEnum;
544+
this.dataFormat = cp.dataFormat;
545+
this.contentType = cp.contentType;
546+
this.collectionFormat = cp.collectionFormat;
547+
this.description = cp.description;
548+
this.unescapedDescription = cp.unescapedDescription;
549+
this.baseType = cp.baseType;
550+
this.defaultValue = cp.defaultValue;
551+
this.enumName = cp.enumName;
552+
this.style = cp.style;
553+
this.nameInLowerCase = cp.nameInLowerCase;
554+
this.example = cp.example;
555+
this.jsonSchema = cp.jsonSchema;
556+
this.isString = cp.isString;
557+
this.isNumeric = cp.isNumeric;
558+
this.isInteger = cp.isInteger;
559+
this.isLong = cp.isLong;
560+
this.isNumber = cp.isNumber;
561+
this.isFloat = cp.isFloat;
562+
this.isDouble = cp.isDouble;
563+
this.isDecimal = cp.isDecimal;
564+
this.isByteArray = cp.isByteArray;
565+
this.isBinary = cp.isBinary;
566+
this.isBoolean = cp.isBoolean;
567+
this.isDate = cp.isDate;
568+
this.isDateTime = cp.isDateTime;
569+
this.isUuid = cp.isUuid;
570+
this.isUri = cp.isUri;
571+
this.isEmail = cp.isEmail;
572+
this.isFreeFormObject = cp.isFreeFormObject;
573+
this.isAnyType = cp.isAnyType;
574+
this.isArray = cp.isArray;
575+
this.isMap = cp.isMap;
576+
this.isFile = cp.isFile;
577+
this.isEnum = cp.isEnum;
578+
this.isEnumRef = cp.isEnumRef;
579+
this._enum = cp._enum;
580+
this.allowableValues = cp.allowableValues;
581+
this.items = cp.items;
582+
this.additionalProperties = cp.additionalProperties;
583+
this.vars = cp.vars;
584+
this.requiredVars = cp.requiredVars;
585+
this.mostInnerItems = cp.mostInnerItems;
586+
this.vendorExtensions = cp.vendorExtensions;
587+
this.hasValidation = cp.hasValidation;
588+
this.isNullable = cp.isNullable;
589+
this.required = cp.required;
590+
this.maximum = cp.maximum;
591+
this.exclusiveMaximum = cp.exclusiveMaximum;
592+
this.minimum = cp.minimum;
593+
this.exclusiveMinimum = cp.exclusiveMinimum;
594+
this.maxLength = cp.maxLength;
595+
this.minLength = cp.minLength;
596+
this.pattern = cp.pattern;
597+
this.maxItems = cp.maxItems;
598+
this.minItems = cp.minItems;
599+
this.uniqueItems = cp.uniqueItems;
600+
this.multipleOf = cp.multipleOf;
601+
this.setHasVars(cp.getHasVars());
602+
this.setHasRequired(cp.getHasRequired());
603+
this.setMaxProperties(cp.getMaxProperties());
604+
this.setMinProperties(cp.getMinProperties());
605+
}
606+
607+
@Override
608+
public ExtendedCodegenParameter copy() {
609+
CodegenParameter superCopy = super.copy();
610+
ExtendedCodegenParameter output = new ExtendedCodegenParameter(superCopy);
611+
output.hasSanitizedName = this.hasSanitizedName;
612+
return output;
613+
}
614+
615+
@Override
616+
public boolean equals(Object o) {
617+
if (o == null)
618+
return false;
619+
620+
if (this.getClass() != o.getClass())
621+
return false;
622+
623+
boolean result = super.equals(o);
624+
ExtendedCodegenParameter that = (ExtendedCodegenParameter) o;
625+
return result && hasSanitizedName == that.hasSanitizedName;
626+
}
627+
628+
@Override
629+
public int hashCode() {
630+
int superHash = super.hashCode();
631+
return Objects.hash(superHash, hasSanitizedName);
632+
}
633+
634+
@Override
635+
public String toString() {
636+
String superString = super.toString();
637+
final StringBuilder sb = new StringBuilder(superString);
638+
sb.append(", hasSanitizedName=").append(hasSanitizedName);
639+
return sb.toString();
640+
}
641+
}
642+
485643
/**
486644
* Validates that the given string value only contains '-', '.' and alpha numeric characters.
487645
* Throws an IllegalArgumentException, if the string contains any other characters.

modules/openapi-generator/src/main/resources/typescript-nestjs/api.service.mustache

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export interface {{classname}}{{operationIdCamelCase}}Request {
3535
* @type {{=<% %>=}}{<%&dataType%>}<%={{ }}=%>
3636
* @memberof {{classname}}{{operationIdCamelCase}}
3737
*/
38-
readonly '{{paramName}}'{{^required}}?{{/required}}: {{{dataType}}}
38+
readonly {{#hasSanitizedName}}'{{{baseName}}}'{{/hasSanitizedName}}{{^hasSanitizedName}}{{{paramName}}}{{/hasSanitizedName}}{{^required}}?{{/required}}: {{{dataType}}}
3939
{{^-last}}
4040

4141
{{/-last}}
@@ -102,13 +102,23 @@ export class {{classname}} {
102102
public {{nickname}}({{#allParams}}{{^isConstEnumParam}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, {{/isConstEnumParam}}{{/allParams}}): Observable<AxiosResponse<{{#returnType}}{{{returnType}}}{{#isResponseTypeFile}}|undefined{{/isResponseTypeFile}}{{/returnType}}{{^returnType}}any{{/returnType}}>>;
103103
public {{nickname}}({{#allParams}}{{^isConstEnumParam}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, {{/isConstEnumParam}}{{/allParams}}): Observable<any> {
104104
{{/useSingleRequestParameter}}
105+
{{#allParams.0}}
106+
{{#useSingleRequestParameter}}
107+
const {
108+
{{#allParams}}
109+
{{#hasSanitizedName}}'{{{baseName}}}': {{/hasSanitizedName}}{{paramName}},
110+
{{/allParams}}
111+
} = requestParameters;
112+
113+
{{/useSingleRequestParameter}}
114+
{{/allParams.0}}
105115
{{#allParams}}
106116
{{#required}}
107117
{{#isConstEnumParam}}
108118
let {{paramName}} = {{{dataType}}};
109119
{{/isConstEnumParam}}
110120
{{^isConstEnumParam}}
111-
if ({{#useSingleRequestParameter}}requestParameters['{{/useSingleRequestParameter}}{{paramName}}{{#useSingleRequestParameter}}']{{/useSingleRequestParameter}} === null || {{#useSingleRequestParameter}}requestParameters['{{/useSingleRequestParameter}}{{paramName}}{{#useSingleRequestParameter}}']{{/useSingleRequestParameter}} === undefined) {
121+
if ({{paramName}} === null || {{paramName}} === undefined) {
112122
throw new Error('Required parameter {{paramName}} was null or undefined when calling {{nickname}}.');
113123
}
114124

@@ -119,24 +129,24 @@ export class {{classname}} {
119129
let queryParameters = new URLSearchParams();
120130
{{#queryParams}}
121131
{{#isArray}}
122-
if ({{#useSingleRequestParameter}}requestParameters['{{/useSingleRequestParameter}}{{paramName}}{{#useSingleRequestParameter}}']{{/useSingleRequestParameter}}) {
132+
if ({{paramName}}) {
123133
{{#isCollectionFormatMulti}}
124-
{{#useSingleRequestParameter}}requestParameters['{{/useSingleRequestParameter}}{{paramName}}{{#useSingleRequestParameter}}']{{/useSingleRequestParameter}}.forEach((element) => {
134+
{{paramName}}.forEach((element) => {
125135
queryParameters.append('{{baseName}}', <any>element);
126136
})
127137
{{/isCollectionFormatMulti}}
128138
{{^isCollectionFormatMulti}}
129-
queryParameters['{{baseName}}'] = {{#useSingleRequestParameter}}requestParameters['{{/useSingleRequestParameter}}{{paramName}}{{#useSingleRequestParameter}}']{{/useSingleRequestParameter}}.join(COLLECTION_FORMATS['{{collectionFormat}}']);
139+
queryParameters['{{baseName}}'] = {{paramName}}.join(COLLECTION_FORMATS['{{collectionFormat}}']);
130140
{{/isCollectionFormatMulti}}
131141
}
132142
{{/isArray}}
133143
{{^isArray}}
134-
if ({{#useSingleRequestParameter}}requestParameters['{{/useSingleRequestParameter}}{{paramName}}{{#useSingleRequestParameter}}']{{/useSingleRequestParameter}} !== undefined && {{#useSingleRequestParameter}}requestParameters['{{/useSingleRequestParameter}}{{paramName}}{{#useSingleRequestParameter}}']{{/useSingleRequestParameter}} !== null) {
144+
if ({{paramName}} !== undefined && {{paramName}} !== null) {
135145
{{#isDateTime}}
136-
queryParameters.append('{{baseName}}', (<any>{{#useSingleRequestParameter}}requestParameters['{{/useSingleRequestParameter}}{{paramName}}{{#useSingleRequestParameter}}']{{/useSingleRequestParameter}}).toISOString());
146+
queryParameters.append('{{baseName}}', (<any>{{paramName}}).toISOString());
137147
{{/isDateTime}}
138148
{{^isDateTime}}
139-
queryParameters.append('{{baseName}}', <any>{{#useSingleRequestParameter}}requestParameters['{{/useSingleRequestParameter}}{{paramName}}{{#useSingleRequestParameter}}']{{/useSingleRequestParameter}});
149+
queryParameters.append('{{baseName}}', <any>{{paramName}});
140150
{{/isDateTime}}
141151
}
142152
{{/isArray}}
@@ -146,13 +156,13 @@ export class {{classname}} {
146156
let headers = {...this.defaultHeaders};
147157
{{#headerParams}}
148158
{{#isArray}}
149-
if ({{#useSingleRequestParameter}}requestParameters['{{/useSingleRequestParameter}}{{paramName}}{{#useSingleRequestParameter}}']{{/useSingleRequestParameter}}) {
150-
headers['{{baseName}}'] = {{#useSingleRequestParameter}}requestParameters['{{/useSingleRequestParameter}}{{paramName}}{{#useSingleRequestParameter}}']{{/useSingleRequestParameter}}.join(COLLECTION_FORMATS['{{collectionFormat}}']);
159+
if ({{paramName}}) {
160+
headers['{{baseName}}'] = {{paramName}}.join(COLLECTION_FORMATS['{{collectionFormat}}']);
151161
}
152162
{{/isArray}}
153163
{{^isArray}}
154-
if ({{#useSingleRequestParameter}}requestParameters['{{/useSingleRequestParameter}}{{paramName}}{{#useSingleRequestParameter}}']{{/useSingleRequestParameter}} !== undefined && {{#useSingleRequestParameter}}requestParameters['{{/useSingleRequestParameter}}{{paramName}}{{#useSingleRequestParameter}}']{{/useSingleRequestParameter}} !== null) {
155-
headers['{{baseName}}'] = String({{#useSingleRequestParameter}}requestParameters['{{/useSingleRequestParameter}}{{paramName}}{{#useSingleRequestParameter}}']{{/useSingleRequestParameter}});
164+
if ({{paramName}} !== undefined && {{paramName}} !== null) {
165+
headers['{{baseName}}'] = String({{paramName}});
156166
}
157167
{{/isArray}}
158168
{{/headerParams}}
@@ -246,20 +256,20 @@ export class {{classname}} {
246256
{{#formParams}}
247257

248258
{{#isArray}}
249-
if ({{#useSingleRequestParameter}}requestParameters['{{/useSingleRequestParameter}}{{paramName}}{{#useSingleRequestParameter}}']{{/useSingleRequestParameter}}) {
259+
if ({{paramName}}) {
250260
{{#isCollectionFormatMulti}}
251-
{{#useSingleRequestParameter}}requestParameters['{{/useSingleRequestParameter}}{{paramName}}{{#useSingleRequestParameter}}']{{/useSingleRequestParameter}}.forEach((element) => {
261+
{{paramName}}.forEach((element) => {
252262
formParams!.append('{{baseName}}', <any>element);
253263
})
254264
{{/isCollectionFormatMulti}}
255265
{{^isCollectionFormatMulti}}
256-
formParams!.append('{{baseName}}', {{#useSingleRequestParameter}}requestParameters['{{/useSingleRequestParameter}}{{paramName}}{{#useSingleRequestParameter}}']{{/useSingleRequestParameter}}.join(COLLECTION_FORMATS['{{collectionFormat}}']));
266+
formParams!.append('{{baseName}}', {{paramName}}.join(COLLECTION_FORMATS['{{collectionFormat}}']));
257267
{{/isCollectionFormatMulti}}
258268
}
259269
{{/isArray}}
260270
{{^isArray}}
261-
if ({{#useSingleRequestParameter}}requestParameters['{{/useSingleRequestParameter}}{{paramName}}{{#useSingleRequestParameter}}']{{/useSingleRequestParameter}} !== undefined) {
262-
formParams!.append('{{baseName}}', <any>{{#useSingleRequestParameter}}requestParameters['{{/useSingleRequestParameter}}{{paramName}}{{#useSingleRequestParameter}}']{{/useSingleRequestParameter}});
271+
if ({{paramName}} !== undefined) {
272+
formParams!.append('{{baseName}}', <any>{{paramName}});
263273
}
264274
{{/isArray}}
265275
{{/formParams}}
@@ -272,7 +282,7 @@ export class {{classname}} {
272282
}
273283

274284
return this.httpClient.{{httpMethod}}{{^isResponseFile}}<{{#returnType}}{{{returnType}}}{{#isResponseTypeFile}}|undefined{{/isResponseTypeFile}}{{/returnType}}{{^returnType}}any{{/returnType}}>{{/isResponseFile}}(`${this.basePath}{{{path}}}`,{{#isBodyAllowed}}
275-
{{#bodyParam}}{{#useSingleRequestParameter}}requestParameters['{{/useSingleRequestParameter}}{{paramName}}{{#useSingleRequestParameter}}']{{/useSingleRequestParameter}}{{/bodyParam}}{{^bodyParam}}{{#hasFormParams}}convertFormParamsToString ? formParams!.toString() : formParams!{{/hasFormParams}}{{^hasFormParams}}null{{/hasFormParams}}{{/bodyParam}},{{/isBodyAllowed}}
285+
{{#bodyParam}}{{paramName}}{{/bodyParam}}{{^bodyParam}}{{#hasFormParams}}convertFormParamsToString ? formParams!.toString() : formParams!{{/hasFormParams}}{{^hasFormParams}}null{{/hasFormParams}}{{/bodyParam}},{{/isBodyAllowed}}
276286
{
277287
{{#hasQueryParams}}
278288
params: queryParameters,

modules/openapi-generator/src/main/resources/typescript-nestjs/modelGeneric.mustache

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ export interface {{classname}}{{#allParents}}{{#-first}} extends {{/-first}}{{{.
55
* {{{.}}}
66
*/
77
{{/description}}
8-
{{#isReadOnly}}readonly {{/isReadOnly}}'{{{name}}}'{{^required}}?{{/required}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{#isNullable}} | null{{/isNullable}};
8+
{{#isReadOnly}}readonly {{/isReadOnly}}{{#hasSanitizedName}}'{{{baseName}}}'{{/hasSanitizedName}}{{^hasSanitizedName}}{{{name}}}{{/hasSanitizedName}}{{^required}}?{{/required}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{#isNullable}} | null{{/isNullable}};
99
{{/vars}}
1010
}{{>modelGenericEnums}}

modules/openapi-generator/src/main/resources/typescript-nestjs/modelTaggedUnion.mustache

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export interface {{classname}} { {{>modelGenericAdditionalProperties}}
1010
* {{{.}}}
1111
*/
1212
{{/description}}
13-
'{{name}}'{{^required}}?{{/required}}: {{#discriminatorValue}}'{{.}}'{{/discriminatorValue}}{{^discriminatorValue}}{{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{/discriminatorValue}}{{#isNullable}} | null{{/isNullable}};
13+
{{#hasSanitizedName}}'{{{baseName}}}'{{/hasSanitizedName}}{{^hasSanitizedName}}{{{name}}}{{/hasSanitizedName}}{{^required}}?{{/required}}: {{#discriminatorValue}}'{{.}}'{{/discriminatorValue}}{{^discriminatorValue}}{{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{/discriminatorValue}}{{#isNullable}} | null{{/isNullable}};
1414
{{/allVars}}
1515
}
1616
{{>modelGenericEnums}}

modules/openapi-generator/src/test/resources/3_0/typescript-nestjs/reserved-param-names.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ paths:
1212
description: ''
1313
operationId: testReservedParamNames
1414
parameters:
15+
- name: notReserved
16+
in: query
17+
description: Should not be treated as a reserved param name
18+
required: true
19+
schema:
20+
type: string
1521
- name: from
1622
in: query
1723
description: Might conflict with rxjs import

samples/client/petstore/typescript-nestjs-v6-provided-in-root/builds/default/model/apiResponse.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
* Describes the result of uploading an image resource
1616
*/
1717
export interface ApiResponse {
18-
'code'?: number;
19-
'type'?: string;
20-
'message'?: string;
18+
code?: number;
19+
type?: string;
20+
message?: string;
2121
}
2222

samples/client/petstore/typescript-nestjs-v6-provided-in-root/builds/default/model/category.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
* A category for a pet
1616
*/
1717
export interface Category {
18-
'id'?: number;
19-
'name'?: string;
18+
id?: number;
19+
name?: string;
2020
}
2121

samples/client/petstore/typescript-nestjs-v6-provided-in-root/builds/default/model/order.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@
1515
* An order for a pets from the pet store
1616
*/
1717
export interface Order {
18-
'id'?: number;
19-
'petId'?: number;
20-
'quantity'?: number;
21-
'shipDate'?: string;
18+
id?: number;
19+
petId?: number;
20+
quantity?: number;
21+
shipDate?: string;
2222
/**
2323
* Order Status
2424
*/
25-
'status'?: Order.StatusEnum;
26-
'complete'?: boolean;
25+
status?: Order.StatusEnum;
26+
complete?: boolean;
2727
}
2828
export namespace Order {
2929
export type StatusEnum = 'placed' | 'approved' | 'delivered';

0 commit comments

Comments
 (0)