Skip to content

Commit cd7f62a

Browse files
v-jiaodiMaryGao
andauthored
Fix the reserved word issue in Modular for API layer (#3191)
* add test * update * fix * Add the UTs for the reserved word * Format codes * Fix the issue in sample gen * Fix the UTs * remove help test * add test case for reserved word operation * fix ci * fix ci * update reserved words list --------- Co-authored-by: Mary Gao <yanmeigao1210@gmail.com>
1 parent 8b914fe commit cd7f62a

12 files changed

Lines changed: 257 additions & 43 deletions

File tree

packages/rlc-common/src/helpers/nameUtils.ts

Lines changed: 50 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -32,69 +32,81 @@ export const ReservedModelNames: ReservedName[] = [
3232
{ name: "as", reservedFor: [NameType.Parameter] },
3333
{ name: "assert", reservedFor: [NameType.Parameter] },
3434
{ name: "async", reservedFor: [NameType.Parameter] },
35-
{ name: "await", reservedFor: [NameType.Parameter] },
35+
{ name: "await", reservedFor: [NameType.Parameter, NameType.Method] },
3636
{ name: "boolean", reservedFor: [NameType.Parameter, ...Newable] },
37-
{ name: "break", reservedFor: [NameType.Parameter] },
38-
{ name: "case", reservedFor: [NameType.Parameter] },
39-
{ name: "catch", reservedFor: [NameType.Parameter] },
40-
{ name: "class", reservedFor: [NameType.Parameter] },
37+
{ name: "break", reservedFor: [NameType.Parameter, NameType.Method] },
38+
{ name: "case", reservedFor: [NameType.Parameter, NameType.Method] },
39+
{ name: "catch", reservedFor: [NameType.Parameter, NameType.Method] },
40+
{ name: "class", reservedFor: [NameType.Parameter, NameType.Method] },
4141
{ name: "const", reservedFor: [NameType.Parameter] },
4242
{ name: "constructor", reservedFor: [NameType.Parameter] },
43-
{ name: "continue", reservedFor: [NameType.Parameter] },
43+
{ name: "continue", reservedFor: [NameType.Parameter, NameType.Method] },
4444
{ name: "date", reservedFor: [NameType.Parameter, ...Newable] },
45-
{ name: "debugger", reservedFor: [NameType.Parameter] },
45+
{ name: "debugger", reservedFor: [NameType.Parameter, NameType.Method] },
4646
{ name: "declare", reservedFor: [NameType.Parameter] },
47-
{ name: "default", reservedFor: [NameType.Parameter] },
48-
{ name: "delete", reservedFor: [NameType.Parameter, NameType.Operation] },
49-
{ name: "do", reservedFor: [NameType.Parameter] },
47+
{ name: "default", reservedFor: [NameType.Parameter, NameType.Method] },
48+
{
49+
name: "delete",
50+
reservedFor: [NameType.Parameter, NameType.Operation, NameType.Method]
51+
},
52+
{ name: "do", reservedFor: [NameType.Parameter, NameType.Method] },
5053
{ name: "else", reservedFor: [NameType.Parameter] },
5154
{ name: "enum", reservedFor: [NameType.Parameter] },
5255
{ name: "error", reservedFor: [NameType.Parameter, ...Newable] },
53-
{ name: "export", reservedFor: [NameType.Parameter, NameType.Operation] },
56+
{
57+
name: "export",
58+
reservedFor: [NameType.Parameter, NameType.Operation, NameType.Method]
59+
},
5460
{ name: "extends", reservedFor: [NameType.Parameter] },
5561
{ name: "false", reservedFor: [NameType.Parameter] },
56-
{ name: "finally", reservedFor: [NameType.Parameter] },
57-
{ name: "for", reservedFor: [NameType.Parameter] },
62+
{ name: "finally", reservedFor: [NameType.Parameter, NameType.Method] },
63+
{ name: "for", reservedFor: [NameType.Parameter, NameType.Method] },
5864
{ name: "from", reservedFor: [NameType.Parameter] },
59-
{ name: "function", reservedFor: [NameType.Parameter, ...Newable] },
65+
{
66+
name: "function",
67+
reservedFor: [NameType.Parameter, ...Newable, NameType.Method]
68+
},
6069
{ name: "get", reservedFor: [NameType.Parameter] },
6170
{ name: "if", reservedFor: [NameType.Parameter] },
6271
{ name: "implements", reservedFor: [NameType.Parameter] },
6372
{ name: "import", reservedFor: [NameType.Parameter] },
64-
{ name: "in", reservedFor: [NameType.Parameter] },
65-
{ name: "instanceof", reservedFor: [NameType.Parameter] },
73+
{ name: "in", reservedFor: [NameType.Parameter, NameType.Method] },
74+
{ name: "instanceof", reservedFor: [NameType.Parameter, NameType.Method] },
6675
{ name: "interface", reservedFor: [NameType.Parameter] },
67-
{ name: "let", reservedFor: [NameType.Parameter] },
76+
{ name: "let", reservedFor: [NameType.Parameter, NameType.Method] },
6877
{ name: "module", reservedFor: [NameType.Parameter] },
69-
{ name: "new", reservedFor: [NameType.Parameter] },
70-
{ name: "null", reservedFor: [NameType.Parameter] },
78+
{ name: "new", reservedFor: [NameType.Parameter, NameType.Method] },
79+
{ name: "null", reservedFor: [NameType.Parameter, NameType.Method] },
7180
{ name: "number", reservedFor: [NameType.Parameter, ...Newable] },
7281
{ name: "of", reservedFor: [NameType.Parameter] },
7382
{ name: "package", reservedFor: [NameType.Parameter] },
7483
{ name: "private", reservedFor: [NameType.Parameter] },
7584
{ name: "protected", reservedFor: [NameType.Parameter] },
76-
{ name: "public", reservedFor: [NameType.Parameter, NameType.Operation] },
85+
{
86+
name: "public",
87+
reservedFor: [NameType.Parameter, NameType.Operation, NameType.Method]
88+
},
7789
{ name: "requestoptions", reservedFor: [NameType.Parameter] },
78-
{ name: "require", reservedFor: [NameType.Parameter] },
90+
{ name: "require", reservedFor: [NameType.Parameter, NameType.Method] },
7991
{ name: "return", reservedFor: [NameType.Parameter] },
8092
{ name: "set", reservedFor: [NameType.Parameter, ...Newable] },
81-
{ name: "static", reservedFor: [NameType.Parameter] },
93+
{ name: "static", reservedFor: [NameType.Parameter, NameType.Method] },
8294
{ name: "string", reservedFor: [NameType.Parameter, ...Newable] },
8395
{ name: "super", reservedFor: [NameType.Parameter] },
84-
{ name: "switch", reservedFor: [NameType.Parameter] },
96+
{ name: "switch", reservedFor: [NameType.Parameter, NameType.Method] },
8597
{ name: "symbol", reservedFor: [NameType.Parameter, ...Newable] },
8698
{ name: "this", reservedFor: [NameType.Parameter] },
87-
{ name: "throw", reservedFor: [NameType.Parameter] },
99+
{ name: "throw", reservedFor: [NameType.Parameter, NameType.Method] },
88100
{ name: "true", reservedFor: [NameType.Parameter] },
89101
{ name: "try", reservedFor: [NameType.Parameter] },
90102
{ name: "type", reservedFor: [NameType.Parameter] },
91103
{ name: "typeof", reservedFor: [NameType.Parameter] },
92-
{ name: "var", reservedFor: [NameType.Parameter] },
104+
{ name: "var", reservedFor: [NameType.Parameter, NameType.Method] },
93105
{ name: "void", reservedFor: [NameType.Parameter] },
94-
{ name: "while", reservedFor: [NameType.Parameter] },
106+
{ name: "while", reservedFor: [NameType.Parameter, NameType.Method] },
95107
{ name: "with", reservedFor: [NameType.Parameter] },
96-
{ name: "yield", reservedFor: [NameType.Parameter] },
97-
{ name: "arguments", reservedFor: [NameType.Parameter] },
108+
{ name: "yield", reservedFor: [NameType.Parameter, NameType.Method] },
109+
{ name: "arguments", reservedFor: [NameType.Parameter, NameType.Method] },
98110
{ name: "global", reservedFor: [...Newable] },
99111
// reserve client for codegen
100112
{ name: "client", reservedFor: [NameType.Parameter] },
@@ -112,30 +124,31 @@ export function guardReservedNames(
112124
nameType: NameType,
113125
customReservedNames: ReservedName[] = []
114126
): string {
115-
const suffix = getSuffix(nameType);
127+
const [prefix, suffix] = getAffix(nameType);
116128
return [...ReservedModelNames, ...customReservedNames]
117129
.filter((r) => r.reservedFor.includes(nameType))
118130
.find((r) => r.name === name.toLowerCase())
119-
? `${name}${suffix}`
131+
? `${prefix}${name}${suffix}`
120132
: name;
121133
}
122134

123-
function getSuffix(nameType?: NameType) {
135+
function getAffix(nameType?: NameType): [string, string] {
124136
switch (nameType) {
125137
case NameType.File:
126138
case NameType.Operation:
127-
return "";
139+
return ["", ""];
128140
case NameType.Property:
129-
return "Property";
141+
return ["", "Property"];
130142
case NameType.OperationGroup:
131-
return "Operations";
143+
return ["", "Operations"];
132144
case NameType.Parameter:
133-
return "Param";
145+
return ["", "Param"];
146+
case NameType.Method:
147+
return ["$", ""];
134148
case NameType.Class:
135149
case NameType.Interface:
136-
case NameType.Method:
137150
default:
138-
return "Model";
151+
return ["", "Model"];
139152
}
140153
}
141154

packages/rlc-common/test/helpers/nameUtils.spec.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,35 @@ describe("#normalizeName", () => {
103103
});
104104
});
105105

106+
describe("for method", () => {
107+
it("should return the name with prefix $ for the method name is a reserved name", () => {
108+
expect(normalizeName("continue", NameType.Method, true)).to.equal(
109+
"$continue"
110+
);
111+
expect(normalizeName("break", NameType.Method, true)).to.equal(
112+
"$break"
113+
);
114+
expect(normalizeName("case", NameType.Method, true)).to.equal(
115+
"$case"
116+
);
117+
expect(normalizeName("break", NameType.Method, true)).to.equal(
118+
"$break"
119+
);
120+
expect(normalizeName("class", NameType.Method, true)).to.equal(
121+
"$class"
122+
);
123+
expect(normalizeName("default", NameType.Method, true)).to.equal(
124+
"$default"
125+
);
126+
expect(normalizeName("do", NameType.Method, true)).to.equal(
127+
"$do"
128+
);
129+
expect(normalizeName("function", NameType.Method, true)).to.equal(
130+
"$function"
131+
);
132+
});
133+
});
134+
106135
describe("for operation", () => {
107136
it("should return the name with the suffix 'Operation' if the name is a reserved name", () => {
108137
expect(normalizeName("export", NameType.Operation,)).to.equal(

packages/typespec-test/test/widget_dpg/generated/typespec-ts/review/widget_dpg.api.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ export interface AnalyzeResult {
1919
summary: string;
2020
}
2121

22+
// @public
23+
export interface BudgetsContinueOptionalParams extends OperationOptions {
24+
}
25+
2226
// @public
2327
export interface BudgetsCreateOrReplaceOptionalParams extends OperationOptions {
2428
updateIntervalInMs?: number;
@@ -30,6 +34,7 @@ export interface BudgetsGetBudgetsOptionalParams extends OperationOptions {
3034

3135
// @public
3236
export interface BudgetsOperations {
37+
continue: (options?: BudgetsContinueOptionalParams) => Promise<void>;
3338
createOrReplace: (name: string, resource: SAPUser, options?: BudgetsCreateOrReplaceOptionalParams) => PollerLike<OperationState<SAPUser>, SAPUser>;
3439
// (undocumented)
3540
getBudgets: (name: string, options?: BudgetsGetBudgetsOptionalParams) => Promise<Widget[]>;
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
export { getBudgets, createOrReplace } from "./operations.js";
4+
export { $continue, getBudgets, createOrReplace } from "./operations.js";
55
export {
6+
BudgetsContinueOptionalParams,
67
BudgetsGetBudgetsOptionalParams,
78
BudgetsCreateOrReplaceOptionalParams,
89
} from "./options.js";

packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/api/budgets/operations.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
sapUserDeserializer,
1212
} from "../../models/models.js";
1313
import {
14+
BudgetsContinueOptionalParams,
1415
BudgetsGetBudgetsOptionalParams,
1516
BudgetsCreateOrReplaceOptionalParams,
1617
} from "./options.js";
@@ -24,6 +25,40 @@ import {
2425
} from "@azure-rest/core-client";
2526
import { PollerLike, OperationState } from "@azure/core-lro";
2627

28+
export function _$continueSend(
29+
context: Client,
30+
options: BudgetsContinueOptionalParams = { requestOptions: {} },
31+
): StreamableMethod {
32+
context.pipeline.removePolicy({ name: "ClientApiVersionPolicy" });
33+
return context
34+
.path("/budgets/widgets/continue")
35+
.get({ ...operationOptionsToRequestParameters(options) });
36+
}
37+
38+
export async function _$continueDeserialize(
39+
result: PathUncheckedResponse,
40+
): Promise<void> {
41+
const expectedStatuses = ["204"];
42+
if (!expectedStatuses.includes(result.status)) {
43+
throw createRestError(result);
44+
}
45+
46+
return;
47+
}
48+
49+
/**
50+
* @fixme continue is a reserved word that cannot be used as an operation name.
51+
* Please add @clientName("clientName") or @clientName("<JS-Specific-Name>", "javascript")
52+
* to the operation to override the generated name.
53+
*/
54+
export async function $continue(
55+
context: Client,
56+
options: BudgetsContinueOptionalParams = { requestOptions: {} },
57+
): Promise<void> {
58+
const result = await _$continueSend(context, options);
59+
return _$continueDeserialize(result);
60+
}
61+
2762
export function _getBudgetsSend(
2863
context: Client,
2964
name: string,

packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/api/budgets/options.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33

44
import { OperationOptions } from "@azure-rest/core-client";
55

6+
/** Optional parameters. */
7+
export interface BudgetsContinueOptionalParams extends OperationOptions {}
8+
69
/** Optional parameters. */
710
export interface BudgetsGetBudgetsOptionalParams extends OperationOptions {}
811

packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/classic/budgets/index.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,25 @@
44
import { SAPWidgetServiceContext } from "../../api/sapWidgetServiceContext.js";
55
import { Widget, SAPUser } from "../../models/models.js";
66
import {
7+
BudgetsContinueOptionalParams,
78
BudgetsGetBudgetsOptionalParams,
89
BudgetsCreateOrReplaceOptionalParams,
910
} from "../../api/budgets/options.js";
10-
import { getBudgets, createOrReplace } from "../../api/budgets/operations.js";
11+
import {
12+
$continue,
13+
getBudgets,
14+
createOrReplace,
15+
} from "../../api/budgets/operations.js";
1116
import { PollerLike, OperationState } from "@azure/core-lro";
1217

1318
/** Interface representing a Budgets operations. */
1419
export interface BudgetsOperations {
20+
/**
21+
* @fixme continue is a reserved word that cannot be used as an operation name.
22+
* Please add @clientName("clientName") or @clientName("<JS-Specific-Name>", "javascript")
23+
* to the operation to override the generated name.
24+
*/
25+
continue: (options?: BudgetsContinueOptionalParams) => Promise<void>;
1526
getBudgets: (
1627
name: string,
1728
options?: BudgetsGetBudgetsOptionalParams,
@@ -26,6 +37,8 @@ export interface BudgetsOperations {
2637

2738
function _getBudgets(context: SAPWidgetServiceContext) {
2839
return {
40+
continue: (options?: BudgetsContinueOptionalParams) =>
41+
$continue(context, options),
2942
getBudgets: (name: string, options?: BudgetsGetBudgetsOptionalParams) =>
3043
getBudgets(context, name, options),
3144
createOrReplace: (

packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export {
1919
} from "./models/index.js";
2020
export { SAPWidgetServiceClientOptionalParams } from "./api/index.js";
2121
export {
22+
BudgetsContinueOptionalParams,
2223
BudgetsGetBudgetsOptionalParams,
2324
BudgetsCreateOrReplaceOptionalParams,
2425
} from "./api/budgets/index.js";

packages/typespec-test/test/widget_dpg/spec/main.tsp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,4 +179,8 @@ interface Budgets {
179179
@get getBudgets(
180180
@query name: string
181181
): Widget[] | WidgetError;
182+
183+
@route("/widgets/continue")
184+
// test the reserved word "continue" operation
185+
op continue(): void;
182186
}

packages/typespec-ts/src/modular/helpers/namingHelpers.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,19 @@ export interface GuardedName {
2727
}
2828

2929
export function getOperationName(operation: ServiceOperation): GuardedName {
30-
if (isReservedName(operation.name, NameType.Operation)) {
30+
const norm = normalizeName(operation.name, NameType.Method, true);
31+
if (isReservedName(operation.name, NameType.Method)) {
3132
return {
32-
name: `$${operation.name}`,
33+
name: norm,
3334
fixme: [
3435
`${operation.name} is a reserved word that cannot be used as an operation name.
3536
Please add @clientName("clientName") or @clientName("<JS-Specific-Name>", "javascript")
3637
to the operation to override the generated name.`
3738
]
3839
};
3940
}
40-
4141
return {
42-
name: normalizeName(operation.name, NameType.Operation, true)
42+
name: norm
4343
};
4444
}
4545

0 commit comments

Comments
 (0)