Skip to content

Commit

Permalink
Organize and add test and documentation for printIdentifier helper (#…
Browse files Browse the repository at this point in the history
…3792)

This is something that is quite useful for any external tool that want
to write some typespec code(Openapi3 to tsp converter for example or api
view)

Edit: actually that was already exported as that exact name directly
from the formatter. This then just adds tests
  • Loading branch information
timotheeguerin committed Jul 10, 2024
1 parent 6b79334 commit 2ebacf5
Show file tree
Hide file tree
Showing 10 changed files with 79 additions and 53 deletions.
6 changes: 6 additions & 0 deletions .chronus/changes/print-id-helper-2024-6-9-15-50-59.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
changeKind: internal
packages:
- "@typespec/compiler"
---

1 change: 0 additions & 1 deletion packages/compiler/src/core/formatter.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { Options } from "prettier";
import { check, format } from "prettier/standalone";
import * as typespecPrettierPlugin from "../formatter/index.js";
export { printId as formatIdentifier } from "../formatter/print/printer.js";

export async function formatTypeSpec(code: string, prettierConfig?: Options): Promise<string> {
const output = await format(code, {
Expand Down
1 change: 1 addition & 0 deletions packages/compiler/src/core/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ export {
// eslint-disable-next-line deprecation/deprecation
stringTemplateToString,
} from "./string-template-utils.js";
export { printIdentifier } from "./syntax-utils.js";
export * from "./type-name-utils.js";
export * from "./usage-resolver.js";
45 changes: 45 additions & 0 deletions packages/compiler/src/core/helpers/syntax-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { isIdentifierContinue, isIdentifierStart, utf16CodeUnits } from "../charcode.js";
import { Keywords } from "../scanner.js";

/**
* Print a string as a TypeSpec identifier. If the string is a valid identifier, return it as is otherwise wrap it into backticks.
* @param sv Identifier string value.
* @returns Identifier string as it would be represented in a TypeSpec file.
*
* @example
* ```ts
* printIdentifier("foo") // foo
* printIdentifier("foo bar") // `foo bar`
* ```
*/
export function printIdentifier(sv: string) {
if (needBacktick(sv)) {
const escapedString = sv
.replace(/\\/g, "\\\\")
.replace(/\n/g, "\\n")
.replace(/\r/g, "\\r")
.replace(/\t/g, "\\t")
.replace(/`/g, "\\`");
return `\`${escapedString}\``;
} else {
return sv;
}
}

function needBacktick(sv: string) {
if (sv.length === 0) {
return false;
}
if (Keywords.has(sv)) {
return true;
}
let cp = sv.codePointAt(0)!;
if (!isIdentifierStart(cp)) {
return true;
}
let pos = 0;
do {
pos += utf16CodeUnits(cp);
} while (pos < sv.length && isIdentifierContinue((cp = sv.codePointAt(pos)!)));
return pos < sv.length;
}
4 changes: 2 additions & 2 deletions packages/compiler/src/core/helpers/type-name-utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { printId } from "../../formatter/print/printer.js";
import { isDefined } from "../../utils/misc.js";
import { isTemplateInstance, isType, isValue } from "../type-utils.js";
import type {
Expand All @@ -16,6 +15,7 @@ import type {
Union,
Value,
} from "../types.js";
import { printIdentifier } from "./syntax-utils.js";

export interface TypeNameOptions {
namespaceFilter?: (ns: Namespace) => boolean;
Expand Down Expand Up @@ -241,7 +241,7 @@ function getOperationName(op: Operation, options: TypeNameOptions | undefined) {
}

function getIdentifierName(name: string, options: TypeNameOptions | undefined) {
return options?.printable ? printId(name) : name;
return options?.printable ? printIdentifier(name) : name;
}

function getStringTemplateName(type: StringTemplate): string {
Expand Down
44 changes: 4 additions & 40 deletions packages/compiler/src/formatter/print/printer.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
import type { AstPath, Doc, Printer } from "prettier";
import { builders } from "prettier/doc";
import {
CharCode,
isIdentifierContinue,
isIdentifierStart,
utf16CodeUnits,
} from "../../core/charcode.js";
import { CharCode } from "../../core/charcode.js";
import { compilerAssert } from "../../core/diagnostics.js";
import { Keywords } from "../../core/scanner.js";
import { printIdentifier as printIdentifierString } from "../../core/helpers/syntax-utils.js";
import {
AliasStatementNode,
ArrayExpressionNode,
Expand Down Expand Up @@ -91,6 +86,7 @@ import { commentHandler } from "./comment-handler.js";
import { needsParens } from "./needs-parens.js";
import { DecorableNode, PrettierChildPrint, TypeSpecPrettierOptions } from "./types.js";
import { util } from "./util.js";

const {
align,
breakParent,
Expand Down Expand Up @@ -1270,39 +1266,7 @@ export function printModelProperty(
}

function printIdentifier(id: IdentifierNode, options: TypeSpecPrettierOptions) {
return printId(id.sv);
}

export function printId(sv: string) {
if (needBacktick(sv)) {
const escapedString = sv
.replace(/\\/g, "\\\\")
.replace(/\n/g, "\\n")
.replace(/\r/g, "\\r")
.replace(/\t/g, "\\t")
.replace(/`/g, "\\`");
return `\`${escapedString}\``;
} else {
return sv;
}
}

function needBacktick(sv: string) {
if (sv.length === 0) {
return false;
}
if (Keywords.has(sv)) {
return true;
}
let cp = sv.codePointAt(0)!;
if (!isIdentifierStart(cp)) {
return true;
}
let pos = 0;
do {
pos += utf16CodeUnits(cp);
} while (pos < sv.length && isIdentifierContinue((cp = sv.codePointAt(pos)!)));
return pos < sv.length;
return printIdentifierString(id.sv);
}

function isModelExpressionInBlock(
Expand Down
4 changes: 2 additions & 2 deletions packages/compiler/src/server/completion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
compilerAssert,
getFirstAncestor,
positionInRange,
printIdentifier,
} from "../core/index.js";
import {
getAnyExtensionFromPath,
Expand All @@ -31,7 +32,6 @@ import {
hasTrailingDirectorySeparator,
resolvePath,
} from "../core/path-utils.js";
import { printId } from "../formatter/print/printer.js";
import { findProjectRoot, loadFile, resolveTspMain } from "../utils/misc.js";
import { getSymbolDetails } from "./type-details.js";

Expand Down Expand Up @@ -435,7 +435,7 @@ function addIdentifierCompletion(
}
: undefined,
kind,
insertText: printId(key) + (suffix ?? ""),
insertText: printIdentifier(key) + (suffix ?? ""),
};
if (deprecated) {
// hide these deprecated items to discourage the usage
Expand Down
14 changes: 7 additions & 7 deletions packages/compiler/src/server/type-signature.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { compilerAssert } from "../core/diagnostics.js";
import { printIdentifier } from "../core/helpers/syntax-utils.js";
import { getEntityName, getTypeName, isStdNamespace } from "../core/helpers/type-name-utils.js";
import type { Program } from "../core/program.js";
import { getFullyQualifiedSymbolName } from "../core/type-utils.js";
Expand All @@ -17,7 +18,6 @@ import {
UnionVariant,
Value,
} from "../core/types.js";
import { printId } from "../formatter/print/printer.js";

/** @internal */
export function getSymbolSignature(program: Program, sym: Sym): string {
Expand Down Expand Up @@ -103,7 +103,7 @@ function getDecoratorSignature(type: Decorator) {
function getFunctionSignature(type: FunctionType) {
const ns = getQualifier(type.namespace);
const parameters = type.parameters.map((x) => getFunctionParameterSignature(x));
return `fn ${ns}${printId(type.name)}(${parameters.join(", ")}): ${getPrintableTypeName(
return `fn ${ns}${printIdentifier(type.name)}(${parameters.join(", ")}): ${getPrintableTypeName(
type.returnType
)}`;
}
Expand All @@ -116,7 +116,7 @@ function getOperationSignature(type: Operation) {
function getFunctionParameterSignature(parameter: FunctionParameter) {
const rest = parameter.rest ? "..." : "";
const optional = parameter.optional ? "?" : "";
return `${rest}${printId(parameter.name)}${optional}: ${getEntityName(parameter.type)}`;
return `${rest}${printIdentifier(parameter.name)}${optional}: ${getEntityName(parameter.type)}`;
}

function getStringTemplateSignature(stringTemplate: StringTemplate) {
Expand All @@ -133,23 +133,23 @@ function getStringTemplateSignature(stringTemplate: StringTemplate) {

function getModelPropertySignature(property: ModelProperty) {
const ns = getQualifier(property.model);
return `${ns}${printId(property.name)}: ${getPrintableTypeName(property.type)}`;
return `${ns}${printIdentifier(property.name)}: ${getPrintableTypeName(property.type)}`;
}

function getUnionVariantSignature(variant: UnionVariant) {
if (typeof variant.name !== "string") {
return getPrintableTypeName(variant.type);
}
const ns = getQualifier(variant.union);
return `${ns}${printId(variant.name)}: ${getPrintableTypeName(variant.type)}`;
return `${ns}${printIdentifier(variant.name)}: ${getPrintableTypeName(variant.type)}`;
}

function getEnumMemberSignature(member: EnumMember) {
const ns = getQualifier(member.enum);
const value = typeof member.value === "string" ? `"${member.value}"` : member.value;
return value === undefined
? `${ns}${printId(member.name)}`
: `${ns}${printId(member.name)}: ${value}`;
? `${ns}${printIdentifier(member.name)}`
: `${ns}${printIdentifier(member.name)}: ${value}`;
}

function getAliasSignature(alias: AliasStatementNode) {
Expand Down
12 changes: 12 additions & 0 deletions packages/compiler/test/core/helpers/syntax-utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { expect, it } from "vitest";
import { printIdentifier } from "../../../src/index.js";

it.each([
["foo", "foo"],
["foo-bar", "`foo-bar`"],
["9test", "`9test`"],
["foo bar", "`foo bar`"],
["foo\nbar", "`foo\\nbar`"],
])("%s -> %s", (a, b) => {
expect(printIdentifier(a)).toEqual(b);
});
1 change: 0 additions & 1 deletion tsconfig.ws.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
{ "path": "packages/openapi3/tsconfig.json" },
{ "path": "packages/monarch/tsconfig.json" },
{ "path": "packages/bundler/tsconfig.json" },
{ "path": "packages/playground-website/tsconfig.json" },
{ "path": "packages/tspd/tsconfig.json" },
{ "path": "packages/samples/tsconfig.json" },
{ "path": "packages/json-schema/tsconfig.json" },
Expand Down

0 comments on commit 2ebacf5

Please sign in to comment.