Skip to content

Commit 1c669b6

Browse files
pshao25Pan Shao
andauthored
Improvement on the comparison tool (#35364)
* Improvement on the comparison tool * update --------- Co-authored-by: Pan Shao <[email protected]>
1 parent bd0107f commit 1c669b6

File tree

10 files changed

+281
-238
lines changed

10 files changed

+281
-238
lines changed

eng/tools/typespec-migration-validation/src/document.ts

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ export function processDocument(document: OpenAPI2Document): OpenAPI2Document {
2323
if (document.tags) {
2424
delete newDocument.tags;
2525
}
26+
if (document.info && document.info["x-typespec-generated"]) {
27+
delete newDocument.info["x-typespec-generated"];
28+
}
2629

2730
for (const route in document.paths) {
2831
const path = document.paths[route] as OpenAPI2PathItem;
@@ -107,17 +110,21 @@ function processOperation(operation: OpenAPI2Operation): OpenAPI2Operation {
107110
delete newOperation["x-ms-pageable"]["itemName"];
108111
}
109112

110-
if (newOperation.produces && newOperation.produces.length === 1 && newOperation.produces[0] === "application/json") {
113+
if (newOperation.produces && (newOperation.produces.length === 1 && newOperation.produces[0] === "application/json" || newOperation.produces.length === 0)) {
111114
delete newOperation.produces;
112115
}
113-
if (newOperation.consumes && newOperation.consumes.length === 1 && newOperation.consumes[0] === "application/json") {
116+
if (newOperation.consumes && (newOperation.consumes.length === 1 && newOperation.consumes[0] === "application/json" || newOperation.consumes.length === 0)) {
114117
delete newOperation.consumes;
115118
}
116119

117120
if (newOperation.tags) {
118121
delete newOperation.tags;
119122
}
120123

124+
if (newOperation.deprecated === false) {
125+
delete newOperation.deprecated;
126+
}
127+
121128
if (configuration.ignoreDescription) {
122129
delete newOperation.description;
123130
delete newOperation.summary;
@@ -196,6 +203,10 @@ function processDefinition(definition: OpenAPI2Schema): OpenAPI2Schema {
196203
delete newDefinition.additionalProperties;
197204
}
198205

206+
if ((newDefinition.properties || newDefinition.additionalProperties) && newDefinition.type === undefined) {
207+
newDefinition.type = "object";
208+
}
209+
199210
if (newDefinition.allOf) {
200211
newDefinition.allOf = newDefinition.allOf.map((item) => {
201212
if ((item as Ref<OpenAPI2Schema>).$ref) {
@@ -226,10 +237,14 @@ function processDefinition(definition: OpenAPI2Schema): OpenAPI2Schema {
226237
}
227238

228239
function processProperty(property: OpenAPI2SchemaProperty): OpenAPI2SchemaProperty {
240+
function isOpenAPI2SchemaRefProperty(prop: OpenAPI2SchemaProperty): prop is OpenAPI2SchemaRefProperty {
241+
return (prop as OpenAPI2SchemaRefProperty).$ref !== undefined;
242+
}
243+
229244
const newProperty: OpenAPI2SchemaProperty = deepCopy(property);
230-
const isRef = (property as OpenAPI2SchemaRefProperty).$ref !== undefined;
245+
const isRef = isOpenAPI2SchemaRefProperty(newProperty);
231246
if (isRef) {
232-
const refPath = (property as OpenAPI2SchemaRefProperty).$ref;
247+
const refPath = newProperty.$ref;
233248
if (refPath.startsWith("#/definitions/")) {
234249
const definitionName = refPath.substring("#/definitions/".length);
235250
const originalDefinition = originalDocument?.definitions?.[definitionName];
@@ -240,9 +255,38 @@ function processProperty(property: OpenAPI2SchemaProperty): OpenAPI2SchemaProper
240255
}
241256
delete (newProperty as any).$ref;
242257
}
258+
else if (originalDefinition?.type && ["boolean", "integer", "number", "string"].includes(originalDefinition.type)) {
259+
delete (newProperty as any).$ref;
260+
for (const key in originalDefinition) {
261+
(newProperty as any)[key] = (originalDefinition as any)[key];
262+
}
263+
}
243264
}
244265
} else {
245-
processEnumInplace(newProperty as OpenAPI2Schema);
266+
if (newProperty.type === "array" && newProperty.items) {
267+
const refPath = (newProperty.items as Ref<OpenAPI2Schema>).$ref;
268+
if (refPath !== undefined) {
269+
if (refPath.startsWith("#/definitions/")) {
270+
const definitionName = refPath.substring("#/definitions/".length);
271+
const originalDefinition = originalDocument?.definitions?.[definitionName];
272+
if (originalDefinition && originalDefinition.enum) {
273+
const processedDefinition = processDefinition(originalDefinition);
274+
for (const key in processedDefinition) {
275+
(newProperty.items as any)[key] = (processedDefinition as any)[key as string];
276+
}
277+
delete (newProperty.items as any).$ref;
278+
}
279+
}
280+
}
281+
else {
282+
processEnumInplace(newProperty.items as OpenAPI2Schema);
283+
}
284+
}
285+
286+
processEnumInplace(newProperty);
287+
if ((newProperty.properties || newProperty.additionalProperties) && newProperty.type === undefined) {
288+
newProperty.type = "object";
289+
}
246290
}
247291

248292
const identifiers = (newProperty as any)["x-ms-identifiers"];

eng/tools/typespec-migration-validation/src/fix/default.ts

Lines changed: 0 additions & 23 deletions
This file was deleted.
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import { Suggestion } from "../jsonOutput.js";
2+
import { constructJsonPath } from "../summary.js";
3+
import { getPropertyName } from "./helper.js";
4+
5+
const knownPropertyDecoratorMapping: { [key: string]: string } = {
6+
'minimum': 'minValue',
7+
'maximum': 'maxValue',
8+
'minLength': 'minLength',
9+
'maxLength': 'maxLength'
10+
};
11+
12+
const addedKey = '__added';
13+
const deletedKey = '__deleted';
14+
15+
export function handleAdded(diff: { path: string, value: string, key: string }): Suggestion | undefined {
16+
const { path, value, key } = diff;
17+
if (key.endsWith(addedKey)) {
18+
const originalKey = key.slice(0, addedKey.length); // Remove '__added' suffix
19+
const property = getPropertyName(path);
20+
21+
if (originalKey === 'x-ms-client-name') {
22+
if (property) {
23+
const [definitionName, propertyName] = property;
24+
return {
25+
suggestion: `Find this TypeSpec statement @@clientName(${definitionName}.${propertyName}, "${value}") in file back-compatible.tsp or client.tsp. Delete this statement.`,
26+
path: constructJsonPath(path, key)
27+
};
28+
}
29+
}
30+
}
31+
32+
return undefined;
33+
}
34+
35+
export function handleDeleted(diff: { path: string, value: string, key: string }): Suggestion | undefined {
36+
const { path, value, key } = diff;
37+
if (key.endsWith(deletedKey)) {
38+
const originalKey = key.slice(0, deletedKey.length); // Remove '__deleted' suffix
39+
const property = getPropertyName(path);
40+
41+
if (originalKey === 'x-ms-client-name') {
42+
if (property) {
43+
const [definitionName, propertyName] = property;
44+
const suggestion = `@@clientName(${definitionName}.${propertyName}, "${value}");`
45+
return {
46+
suggestion: `Find file "back-compatible.tsp" or "client.tsp" and add the following statement exactly as it is::
47+
\`\`\`typespec
48+
${suggestion}
49+
\`\`\``,
50+
path: constructJsonPath(path, key)
51+
};
52+
}
53+
}
54+
else if (originalKey === 'x-ms-client-flatten') {
55+
if ((value as any) === true && property) {
56+
const [definitionName, propertyName] = property;
57+
const suggestion = `@@flattenProperty(${definitionName}.${propertyName});`;
58+
return {
59+
suggestion: `Find file "back-compatible.tsp" or "client.tsp" and add the following statement exactly as it is::
60+
\`\`\`typespec
61+
${suggestion}
62+
\`\`\``,
63+
path: constructJsonPath(path, key)
64+
};
65+
}
66+
}
67+
else if (Object.keys(knownPropertyDecoratorMapping).includes(originalKey)) {
68+
const decoratorName = knownPropertyDecoratorMapping[originalKey];
69+
if (property) {
70+
const [definitionName, propertyName] = property;
71+
return {
72+
suggestion: `Find a model called "${definitionName}". Add \`@${decoratorName}(${value})\` onto its property "${propertyName}". If the property cannot access directly, add \`@@${decoratorName}(${definitionName}.${propertyName}, ${value});\` right after the model.`,
73+
path: constructJsonPath(path, key)
74+
};
75+
}
76+
}
77+
else if (originalKey === 'x-nullable') {
78+
if ((value as any) === true && property) {
79+
const [definitionName, propertyName] = property;
80+
return {
81+
suggestion: `Find a model called "${definitionName}". Change its property "${propertyName}" by adding \` | null\` to its property type.`,
82+
path: constructJsonPath(path, key)
83+
};
84+
}
85+
}
86+
else if (originalKey === "readOnly") {
87+
if ((value as any) === true && property) {
88+
const [definitionName, propertyName] = property;
89+
return {
90+
suggestion: `Find a model called "${definitionName}". Add \`@visibility(Lifecycle.Read)\` onto its property "${propertyName}". If the property cannot access directly, add \`@@visibility(${definitionName}.${propertyName}, Lifecycle.Read);\` RIGHT AFTER the end bracket of the model.`,
91+
path: constructJsonPath(path, key)
92+
};
93+
}
94+
}
95+
else if (originalKey === "x-ms-secret") {
96+
if ((value as any) === true && property) {
97+
const [definitionName, propertyName] = property;
98+
return {
99+
suggestion: `Find a model called "${definitionName}". Add \`@secret\` onto its property "${propertyName}". If the property cannot access directly, add \`@@secret(${definitionName}.${propertyName});\` right after the model.`,
100+
path: constructJsonPath(path, key)
101+
};
102+
}
103+
}
104+
else if (originalKey === "default") {
105+
if (property) {
106+
const [definitionName, propertyName] = property;
107+
return {
108+
suggestion: `Find a model called "${definitionName}". Change its property "${propertyName}" by adding \` = ${typeof value === "string" ? `"${value}"` : value}\`.`,
109+
path: constructJsonPath(path, key)
110+
};
111+
}
112+
}
113+
}
114+
115+
return undefined;
116+
}
117+
118+
export function handleChanged(diff: { path: string, oldValue: string, newValue: string, key: string }): Suggestion | undefined {
119+
const { path, oldValue, newValue, key } = diff;
120+
const property = getPropertyName(path);
121+
if (key === 'x-ms-client-name') {
122+
if (property) {
123+
const [definitionName, propertyName] = property;
124+
return {
125+
suggestion: `Find this TypeSpec statement @@clientName(${definitionName}.${propertyName}, "${newValue}") in file back-compatible.tsp or client.tsp. Change it to @@clientName(${definitionName}.${propertyName}, "${oldValue}")`,
126+
path: path
127+
};
128+
}
129+
}
130+
return undefined;
131+
}

eng/tools/typespec-migration-validation/src/fix/helper.ts

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,72 @@
1+
export function checkElementAddedOrDeleted(jsonObj: any, currentPath: string = ''): Array<{path: string, value: string, key: string}> {
2+
const results: Array<{path: string, value: string, key: string}> = [];
3+
4+
if (!jsonObj || typeof jsonObj !== 'object') {
5+
return results;
6+
}
7+
8+
for (const key in jsonObj) {
9+
if (!Object.prototype.hasOwnProperty.call(jsonObj, key)) {
10+
continue;
11+
}
12+
13+
const newPath = currentPath ? `${currentPath}.${key}` : key;
14+
15+
if (key.endsWith('__deleted') || key.endsWith('__added')) {
16+
// Store both the path and the value
17+
results.push({
18+
path: currentPath, // Use parent path since we're interested in the property that has this extension
19+
value: jsonObj[key],
20+
key: key
21+
});
22+
}
23+
24+
// If value is an object or array, recursively search it
25+
if (jsonObj[key] && typeof jsonObj[key] === 'object') {
26+
const nestedResults = checkElementAddedOrDeleted(jsonObj[key], newPath);
27+
results.push(...nestedResults);
28+
}
29+
}
30+
31+
return results;
32+
}
33+
34+
export function checkElementChanged(jsonObj: any, currentPath: string = ''): Array<{path: string, oldValue: string, newValue: string, key: string}> {
35+
const results: Array<{path: string, oldValue: string, newValue: string, key: string}> = [];
36+
37+
if (!jsonObj || typeof jsonObj !== 'object') {
38+
return results;
39+
}
40+
41+
for (const key in jsonObj) {
42+
if (!Object.prototype.hasOwnProperty.call(jsonObj, key)) {
43+
continue;
44+
}
45+
46+
const newPath = currentPath ? `${currentPath}.${key}` : key;
47+
48+
if (typeof jsonObj[key] === 'object' &&
49+
jsonObj[key]['__old'] !== undefined &&
50+
jsonObj[key]['__new'] !== undefined) {
51+
// Store the path, old value and new value
52+
results.push({
53+
path: currentPath, // Use parent path since we're interested in the property that has this extension
54+
oldValue: jsonObj[key]['__old'],
55+
newValue: jsonObj[key]['__new'],
56+
key: key
57+
});
58+
}
59+
60+
// If value is an object or array, recursively search it
61+
if (jsonObj[key] && typeof jsonObj[key] === 'object') {
62+
const nestedResults = checkElementChanged(jsonObj[key], newPath);
63+
results.push(...nestedResults);
64+
}
65+
}
66+
67+
return results;
68+
}
69+
170
export function checkPropertyAttributeDeleted(checkKey: string, jsonObj: any, currentPath: string = ''): Array<{path: string, value: string, key: string}> {
271
const results: Array<{path: string, value: string, key: string}> = [];
372

@@ -72,7 +141,6 @@ export function checkPropertyAttributeChanged(checkKey: string, jsonObj: any, cu
72141

73142
const newPath = currentPath ? `${currentPath}.${key}` : key;
74143

75-
// Check if this is an x-ms-client-name with __old and __new properties
76144
if (key === checkKey &&
77145
typeof jsonObj[key] === 'object' &&
78146
jsonObj[key]['__old'] !== undefined &&

eng/tools/typespec-migration-validation/src/fix/minMax.ts

Lines changed: 0 additions & 32 deletions
This file was deleted.

0 commit comments

Comments
 (0)