Skip to content

Commit 7b1ce4b

Browse files
author
iscai-msft
committed
add reserved property words to python
1 parent ec0ff16 commit 7b1ce4b

3 files changed

Lines changed: 150 additions & 4 deletions

File tree

packages/typespec-client-generator-core/src/rules/reserved-words/create-reserved-word-rule.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,21 @@ export function createReservedWordRule(
6161
}
6262
}
6363

64+
const parameterModels = new WeakSet<Model>();
65+
6466
return {
6567
model: (node: Model) => check(node, reservedWords.model, "model"),
66-
modelProperty: (node: ModelProperty) => check(node, reservedWords.property, "property"),
67-
operation: (node: Operation) => check(node, reservedWords.operation, "operation"),
68+
modelProperty: (node: ModelProperty) => {
69+
if (node.model && parameterModels.has(node.model)) {
70+
check(node, reservedWords.parameter, "parameter");
71+
} else {
72+
check(node, reservedWords.property, "property");
73+
}
74+
},
75+
operation: (node: Operation) => {
76+
check(node, reservedWords.operation, "operation");
77+
parameterModels.add(node.parameters);
78+
},
6879
enum: (node: Enum) => {
6980
check(node, reservedWords.enumType, "enum");
7081
for (const member of node.members.values()) {

packages/typespec-client-generator-core/src/rules/reserved-words/words.ts

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ export interface LanguageReservedWords {
1010
readonly model: ReadonlySet<string>;
1111
/** Words reserved when used as property names */
1212
readonly property: ReadonlySet<string>;
13+
/** Words reserved when used as operation parameter names */
14+
readonly parameter: ReadonlySet<string>;
1315
/** Words reserved when used as operation/method names */
1416
readonly operation: ReadonlySet<string>;
1517
/** Words reserved when used as enum/union type names */
@@ -25,7 +27,14 @@ export interface LanguageReservedWords {
2527
*/
2628
function createFlatReservedWords(words: readonly string[]): LanguageReservedWords {
2729
const set: ReadonlySet<string> = new Set(words);
28-
return { model: set, property: set, operation: set, enumType: set, enumMember: set };
30+
return {
31+
model: set,
32+
property: set,
33+
parameter: set,
34+
operation: set,
35+
enumType: set,
36+
enumMember: set,
37+
};
2938
}
3039

3140
// ---------------------------------------------------------------------------
@@ -69,9 +78,83 @@ const pythonAlwaysReserved: readonly string[] = [
6978
"yield",
7079
];
7180

81+
// Source: RESERVED_TSP_MODEL_PROPERTIES in python_mappings.py
82+
// These are Python dict/model base-class method names that conflict with
83+
// property names because Azure SDK models inherit from a dict-like base.
84+
const pythonReservedPropertyWords: readonly string[] = [
85+
"self",
86+
"keys",
87+
"items",
88+
"values",
89+
"popitem",
90+
"clear",
91+
"update",
92+
"setdefault",
93+
"pop",
94+
"get",
95+
"copy",
96+
"as_dict",
97+
"datetime",
98+
];
99+
100+
// Source: RESERVED_WORDS[PadType.PARAMETER] + TSP_RESERVED_WORDS[PadType.PARAMETER]
101+
// in python_mappings.py
102+
const pythonReservedParameterWords: readonly string[] = [
103+
"self",
104+
"cls",
105+
// SDK operation kwargs
106+
"content_type",
107+
"accept",
108+
"polling",
109+
"continuation_token",
110+
// transport kwargs
111+
// https://github.com/Azure/azure-sdk-for-python/blob/master/sdk/core/azure-core/CLIENT_LIBRARY_DEVELOPER.md#transport
112+
"connection_timeout",
113+
"connection_verify",
114+
"connection_cert",
115+
"connection_data_block_size",
116+
"use_env_settings",
117+
"read_timeout",
118+
"proxies",
119+
"cookies",
120+
// policy kwargs
121+
// https://github.com/Azure/azure-sdk-for-python/blob/master/sdk/core/azure-core/CLIENT_LIBRARY_DEVELOPER.md#available-policies
122+
"base_headers",
123+
"headers",
124+
"request_id",
125+
"auto_request_id",
126+
"base_user_agent",
127+
"user_agent",
128+
"user_agent_overwrite",
129+
"user_agent_use_env",
130+
"sdk_moniker",
131+
"logging_enable",
132+
"logger",
133+
"response_encoding",
134+
"raw_request_hook",
135+
"raw_response_hook",
136+
"network_span_namer",
137+
"tracing_attributes",
138+
"permit_redirects",
139+
"redirect_max",
140+
"redirect_remove_headers",
141+
"redirect_on_status_codes",
142+
"retry_total",
143+
"retry_connect",
144+
"retry_read",
145+
"retry_status",
146+
"retry_backoff_factor",
147+
"retry_backoff_max",
148+
"retry_mode",
149+
"retry_on_status_codes",
150+
// TSP-specific
151+
"stream",
152+
];
153+
72154
export const pythonReservedWords: LanguageReservedWords = {
73155
model: new Set([...pythonAlwaysReserved, "enum"]),
74-
property: new Set([...pythonAlwaysReserved, "self"]),
156+
property: new Set([...pythonAlwaysReserved, ...pythonReservedPropertyWords]),
157+
parameter: new Set([...pythonAlwaysReserved, ...pythonReservedParameterWords]),
75158
operation: new Set([...pythonAlwaysReserved]),
76159
enumType: new Set([...pythonAlwaysReserved, "enum"]),
77160
enumMember: new Set([...pythonAlwaysReserved]),

packages/typespec-client-generator-core/test/rules/reserved-words.test.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ function expectedMessage(name: string, context: string, language: string): strin
2020
const testWords: LanguageReservedWords = {
2121
model: new Set(["reserved"]),
2222
property: new Set(["reserved"]),
23+
parameter: new Set(["reserved"]),
2324
operation: new Set(["reserved"]),
2425
enumType: new Set(["reserved"]),
2526
enumMember: new Set(["reserved"]),
@@ -30,6 +31,7 @@ const testRule = createReservedWordRule("test", "Test", testWords);
3031
const contextIsolatedWords: LanguageReservedWords = {
3132
model: new Set(["modelonly"]),
3233
property: new Set(["proponly"]),
34+
parameter: new Set(["paramonly"]),
3335
operation: new Set(["oponly"]),
3436
enumType: new Set(["enumtypeonly"]),
3537
enumMember: new Set(["enummemberonly"]),
@@ -70,6 +72,13 @@ describe("reserved word rule factory", () => {
7072
});
7173
});
7274

75+
it("emits warning for reserved parameter name", async () => {
76+
await tester.expect(`op foo(reserved: string): void;`).toEmitDiagnostics({
77+
code: "@azure-tools/typespec-client-generator-core/reserved-words-test",
78+
message: expectedMessage("reserved", "parameter", "Test"),
79+
});
80+
});
81+
7382
it("emits warning for reserved enum type name", async () => {
7483
await tester.expect(`enum reserved { a, b }`).toEmitDiagnostics({
7584
code: "@azure-tools/typespec-client-generator-core/reserved-words-test",
@@ -199,6 +208,27 @@ describe("context isolation", () => {
199208
await tester.expect(`model oponly { name: string; }`).toBeValid();
200209
});
201210

211+
// -- parameter-only word --
212+
213+
it("warns when parameter-only reserved word is used as a parameter name", async () => {
214+
await tester.expect(`op foo(paramonly: string): void;`).toEmitDiagnostics({
215+
code: "@azure-tools/typespec-client-generator-core/reserved-words-isolated",
216+
message: expectedMessage("paramonly", "parameter", "Isolated"),
217+
});
218+
});
219+
220+
it("does not warn when parameter-only reserved word is used as a property name", async () => {
221+
await tester.expect(`model Foo { paramonly: string; }`).toBeValid();
222+
});
223+
224+
it("does not warn when parameter-only reserved word is used as an operation name", async () => {
225+
await tester.expect(`op paramonly(): void;`).toBeValid();
226+
});
227+
228+
it("does not warn when parameter-only reserved word is used as a model name", async () => {
229+
await tester.expect(`model paramonly { name: string; }`).toBeValid();
230+
});
231+
202232
// -- enumType-only word --
203233

204234
it("warns when enumType-only reserved word is used as an enum type name", async () => {
@@ -282,6 +312,28 @@ describe("python reserved words", () => {
282312
});
283313
});
284314

315+
it("emits warning for 'keys' used as property name (Python dict method conflict)", async () => {
316+
await tester.expect(`model Foo { keys: string; }`).toEmitDiagnostics({
317+
code: "@azure-tools/typespec-client-generator-core/reserved-words-python",
318+
message: expectedMessage("keys", "property", "Python"),
319+
});
320+
});
321+
322+
it("'keys' does not warn as an operation name (property-only reserved word)", async () => {
323+
await tester.expect(`op keys(): void;`).toBeValid();
324+
});
325+
326+
it("emits warning for 'stream' used as parameter name (Python TSP-specific)", async () => {
327+
await tester.expect(`op foo(stream: string): void;`).toEmitDiagnostics({
328+
code: "@azure-tools/typespec-client-generator-core/reserved-words-python",
329+
message: expectedMessage("stream", "parameter", "Python"),
330+
});
331+
});
332+
333+
it("'stream' does not warn as a property name (parameter-only reserved word)", async () => {
334+
await tester.expect(`model Foo { stream: string; }`).toBeValid();
335+
});
336+
285337
it("is valid when @clientName resolves conflict for python scope", async () => {
286338
await tester
287339
.expect(`model Foo { @clientName("yieldValue", "python") \`yield\`: string; }`)

0 commit comments

Comments
 (0)