@typespec/http-server-js
Version:
TypeSpec HTTP server code generator for JavaScript
167 lines • 5.91 kB
JavaScript
// Copyright (c) Microsoft Corporation
// Licensed under the MIT license.
import { KEYWORDS } from "./keywords.js";
/**
* Separators recognized by the case parser.
*/
const SEPARATORS = /[\s:_\-./\\]/;
/**
* Separators that are considered unspeakable.
*/
const UNSPEAKABLE_SEPARATORS = /[\s:\-./\\]/;
/**
* Returns true if a name cannot be spoken. A name is unspeakable if:
*
* - It contains only separators and whitespace.
*
* OR
*
* - The first non-separator, non-whitespace character is a digit.
*
* @param name - a name in any case
* @returns true if the name is unspeakable
*/
export function isUnspeakable(name) {
for (const c of name) {
if (!UNSPEAKABLE_SEPARATORS.test(c)) {
return /[0-9]/.test(c);
}
}
return true;
}
const JS_IDENTIFIER = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
/**
* Returns the property name to be used in an object literal.
*/
export function objectLiteralProperty(name) {
if (!JS_IDENTIFIER.test(name) || KEYWORDS.has(name)) {
return JSON.stringify(name);
}
else {
return name;
}
}
/**
* Returns an access expression for a given subject and key.
*
* If the access can be performed using dot notation, it will. Otherwise, bracket notation will be used.
*
* @param subject - the expression to access
* @param key - the key to access within the subject, must be an index value literal, not an expression
*/
export function access(subject, key) {
subject = JS_IDENTIFIER.test(subject) ? subject : `(${subject})`;
if (typeof key === "string" && JS_IDENTIFIER.test(key)) {
return `${subject}.${key}`;
}
else {
return `${subject}[${JSON.stringify(key)}]`;
}
}
/**
* Destructures a name into its components.
*
* The following case conventions are supported:
* - PascalCase (["pascal", "case"])
* - camelCase (["camel", "case"])
* - snake_case (["snake", "case"])
* - kebab-case (["kebab", "case"])
* - dot.separated (["dot", "separated"])
* - path/separated (["path", "separated"])
* - double::colon::separated (["double", "colon", "separated"])
* - space separated (["space", "separated"])
*
* - AND any combination of the above, or any other separators or combination of separators.
*
* @param name - a name in any case
*/
export function parseCase(name) {
const components = [];
let currentComponent = "";
let inAcronym = false;
let inLeadingUnderscore = false;
for (let i = 0; i < name.length; i++) {
const char = name[i];
// cSpell:ignore presponse
// Special case acronym handling. We want to treat acronyms as a single component,
// but we also want the last capitalized letter in an all caps sequence to start a new
// component if the next letter is lower case.
// For example : "HTTPResponse" => ["http", "response"]
// : "OpenAIContext" => ["open", "ai", "context"]
// but : "HTTPresponse" (wrong) => ["htt", "presponse"]
// however : "HTTP_response" (okay I guess) => ["http", "response"]
// allow leading underscores to be part of the first component
if (!components.length && char === "_") {
inLeadingUnderscore = true;
currentComponent += char;
continue;
}
else if (inLeadingUnderscore && char !== "_") {
inLeadingUnderscore = false;
components.push(currentComponent);
currentComponent = "";
}
// If the character is a separator or an upper case character, we push the current component and start a new one.
if (char === char.toUpperCase() && !/[0-9]/.test(char)) {
// If we're in an acronym, we need to check if the next character is lower case.
// If it is, then this is the start of a new component.
const acronymRestart = inAcronym && /[A-Z]/.test(char) && i + 1 < name.length && /[a-z]/.test(name[i + 1]);
if (currentComponent.length > 0 && (acronymRestart || !inAcronym)) {
components.push(currentComponent.trim());
currentComponent = "";
}
}
if (!SEPARATORS.test(char)) {
currentComponent += char.toLowerCase();
}
inAcronym = /[A-Z]/.test(char);
}
if (currentComponent.length > 0) {
components.push(currentComponent);
}
return recase(components);
}
function recase(components) {
const hasLeadingUnderscores = components.length > 0 && components[0].startsWith("_");
function joinComponents(joiner) {
if (hasLeadingUnderscores) {
// The first component contains the leading underscores, so no need to add one.
return `${components[0]}${components.slice(1).join(joiner)}`;
}
return components.join(joiner);
}
return Object.freeze({
components,
get pascalCase() {
return components
.map((component) => component[0].toUpperCase() + component.slice(1))
.join("");
},
get camelCase() {
return components
.map((component, index) => index === 0 || (hasLeadingUnderscores && index === 1)
? component
: component[0].toUpperCase() + component.slice(1))
.join("");
},
get snakeCase() {
return joinComponents("_");
},
get kebabCase() {
return joinComponents("-");
},
get dotCase() {
return joinComponents(".");
},
get pathCase() {
return joinComponents("/");
},
get upper() {
return recase(components.map((component) => component.toUpperCase()));
},
join(separator) {
return joinComponents(separator);
},
});
}
//# sourceMappingURL=case.js.map