Skip to content

Commit

Permalink
Snake case naming optimization (with vendor code from `@azure-tools/c…
Browse files Browse the repository at this point in the history
…odegen`) (#2810)

* init

* debug for vitest

* fix

* regenerate

* fix test

* fix

* fix ci
  • Loading branch information
msyyc authored Sep 5, 2024
1 parent 87c63ec commit 43e3a4c
Show file tree
Hide file tree
Showing 23 changed files with 1,207 additions and 254 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: feature
packages:
- "@azure-tools/typespec-python"
---

Optimize snake-case naming rule
9 changes: 9 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,15 @@
]
},
{
"type": "node",
"request": "launch",
"name": "Debug Current Test File",
"autoAttachChildProcesses": true,
"skipFiles": ["<node_internals>/**"],
"program": "${workspaceFolder}/packages/typespec-python/node_modules/vitest/vitest.mjs",
"args": ["run", "${relativeFile}"],
"smartStep": true
},
"name": "npm: regenerate",
"request": "launch",
"type": "node",
Expand Down
5 changes: 3 additions & 2 deletions packages/typespec-python/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"install": "tsx ./scripts/run-python3.ts ./scripts/install.py",
"prepare": "tsx ./scripts/run-python3.ts ./scripts/prepare.py",
"regenerate": "tsx ./scripts/eng/regenerate.ts",
"test": "tsx ./scripts/eng/run-tests.ts"
"test": "npx vitest run ./test && tsx ./scripts/eng/run-tests.ts"
},
"files": [
"dist/**",
Expand Down Expand Up @@ -74,8 +74,9 @@
"@typespec/eslint-config-typespec": "~0.55.0",
"@typespec/openapi": "~0.59.0",
"c8": "~7.13.0",
"vitest": "^2.0.4",
"rimraf": "~5.0.0",
"typescript": "~5.1.3",
"typescript": "~5.5.4",
"@azure-tools/typespec-azure-core": "~0.45.0",
"@azure-tools/typespec-client-generator-core": "0.45.4",
"@typespec/compiler": "~0.59.1",
Expand Down
79 changes: 72 additions & 7 deletions packages/typespec-python/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,80 @@ import { getSimpleTypeResult, getType } from "./types.js";
import { getNamespaceFullName } from "@typespec/compiler";
import { PythonSdkContext } from "./lib.js";

function IsFullyUpperCase(identifier: string, maxUppercasePreserve: number) {
const len = identifier.length;
if (len > 1) {
if (len <= maxUppercasePreserve && identifier === identifier.toUpperCase()) {
return true;
}

if (len <= maxUppercasePreserve + 1 && identifier.endsWith("s")) {
const i = identifier.substring(0, len - 1);
if (i.toUpperCase() === i) {
return true;
}
}
}
return false;
}

function deconstruct(identifier: string | Array<string>, maxUppercasePreserve: number): Array<string> {
if (Array.isArray(identifier)) {
return [...identifier.flatMap((each) => deconstruct(each, maxUppercasePreserve))];
}

return `${identifier}`
.replace(/([a-z]+)([A-Z])/g, "$1 $2") // Add a space in between camelCase words(e.g. fooBar => foo Bar)
.replace(/(\d+)/g, " $1 ") // Adds a space after numbers(e.g. foo123 => foo123 bar)
.replace(/\b([A-Z]+)([A-Z])s([^a-z])(.*)/g, "$1$2« $3$4") // Add a space after a plural uppper cased word(e.g. MBsFoo => MBs Foo)
.replace(/\b([A-Z]+)([A-Z])([a-z]+)/g, "$1 $2$3") // Add a space between an upper case word(2 char+) and the last captial case.(e.g. SQLConnection -> SQL Connection)
.replace(/«/g, "s")
.trim()
.split(/[\W|_]+/)
.map((each) => (IsFullyUpperCase(each, maxUppercasePreserve) ? each : each.toLowerCase()));
}

function isEqual(s1: string, s2: string): boolean {
// when s2 is undefined and s1 is the string 'undefined', it returns 0, making this true.
// To prevent that, first we need to check if s2 is undefined.
return s2 !== undefined && !!s1 && !s1.localeCompare(s2, undefined, { sensitivity: "base" });
}

function removeSequentialDuplicates(identifier: Iterable<string>) {
const ids = [...identifier].filter((each) => !!each);
for (let i = 0; i < ids.length; i++) {
while (isEqual(ids[i], ids[i - 1])) {
ids.splice(i, 1);
}
while (isEqual(ids[i], ids[i - 2]) && isEqual(ids[i + 1], ids[i - 1])) {
ids.splice(i, 2);
}
}

return ids;
}

function normalize(
identifier: string | Array<string>,
removeDuplicates = true,
maxUppercasePreserve = 0,
): Array<string> {
if (!identifier || identifier.length === 0) {
return [""];
}
return typeof identifier === "string"
? normalize(deconstruct(identifier, maxUppercasePreserve), removeDuplicates, maxUppercasePreserve)
: removeDuplicates
? removeSequentialDuplicates(identifier)
: identifier;
}

export function camelToSnakeCase(name: string): string {
if (!name) return name;
const camelToSnakeCaseRe = (str: string) =>
str
.replace(/[^a-zA-Z0-9]/g, "_")
.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`)
.replace(/_+/g, "_");

return camelToSnakeCaseRe(name[0].toLowerCase() + name.slice(1));
const words = normalize(name, false, 6);
const result = words.join("_").toLowerCase();
const result_final = result.replace(/([^\d])_(\d+)/g, "$1$2");
return result_final;
}

export function removeUnderscoresFromNamespace(name?: string): string {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,25 @@
"encode.bytes.models.DefaultBytesProperty": "Encode.Bytes.DefaultBytesProperty",
"encode.bytes.BytesClient.query.default": "Encode.Bytes.Query.default",
"encode.bytes.BytesClient.query.base64": "Encode.Bytes.Query.base64",
"encode.bytes.BytesClient.query.base64url": "Encode.Bytes.Query.base64url",
"encode.bytes.BytesClient.query.base64url_array": "Encode.Bytes.Query.base64urlArray",
"encode.bytes.BytesClient.query.base64_url": "Encode.Bytes.Query.base64url",
"encode.bytes.BytesClient.query.base64_url_array": "Encode.Bytes.Query.base64urlArray",
"encode.bytes.BytesClient.property.default": "Encode.Bytes.Property.default",
"encode.bytes.BytesClient.property.base64": "Encode.Bytes.Property.base64",
"encode.bytes.BytesClient.property.base64url": "Encode.Bytes.Property.base64url",
"encode.bytes.BytesClient.property.base64url_array": "Encode.Bytes.Property.base64urlArray",
"encode.bytes.BytesClient.property.base64_url": "Encode.Bytes.Property.base64url",
"encode.bytes.BytesClient.property.base64_url_array": "Encode.Bytes.Property.base64urlArray",
"encode.bytes.BytesClient.header.default": "Encode.Bytes.Header.default",
"encode.bytes.BytesClient.header.base64": "Encode.Bytes.Header.base64",
"encode.bytes.BytesClient.header.base64url": "Encode.Bytes.Header.base64url",
"encode.bytes.BytesClient.header.base64url_array": "Encode.Bytes.Header.base64urlArray",
"encode.bytes.BytesClient.header.base64_url": "Encode.Bytes.Header.base64url",
"encode.bytes.BytesClient.header.base64_url_array": "Encode.Bytes.Header.base64urlArray",
"encode.bytes.BytesClient.request_body.default": "Encode.Bytes.RequestBody.default",
"encode.bytes.BytesClient.request_body.octet_stream": "Encode.Bytes.RequestBody.octetStream",
"encode.bytes.BytesClient.request_body.custom_content_type": "Encode.Bytes.RequestBody.customContentType",
"encode.bytes.BytesClient.request_body.base64": "Encode.Bytes.RequestBody.base64",
"encode.bytes.BytesClient.request_body.base64url": "Encode.Bytes.RequestBody.base64url",
"encode.bytes.BytesClient.request_body.base64_url": "Encode.Bytes.RequestBody.base64url",
"encode.bytes.BytesClient.response_body.default": "Encode.Bytes.ResponseBody.default",
"encode.bytes.BytesClient.response_body.octet_stream": "Encode.Bytes.ResponseBody.octetStream",
"encode.bytes.BytesClient.response_body.custom_content_type": "Encode.Bytes.ResponseBody.customContentType",
"encode.bytes.BytesClient.response_body.base64": "Encode.Bytes.ResponseBody.base64",
"encode.bytes.BytesClient.response_body.base64url": "Encode.Bytes.ResponseBody.base64url"
"encode.bytes.BytesClient.response_body.base64_url": "Encode.Bytes.ResponseBody.base64url"
}
}
Loading

0 comments on commit 43e3a4c

Please sign in to comment.