Skip to content

Commit

Permalink
Merge pull request #476 from yegortokmakov/doublequote
Browse files Browse the repository at this point in the history
Option to use double quotes (") as escape character
  • Loading branch information
mrjono1 authored Aug 5, 2024
2 parents 66ce630 + abc3501 commit efc9b8c
Show file tree
Hide file tree
Showing 6 changed files with 192 additions and 11 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,11 @@ export interface Settings {
* @default ' ' (two spaces)
*/
indentationChacters: string;
/**
* If set to true, will use double quotes for strings
* @default false
*/
doublequoteEscape: boolean;
/**
* If a field has a default and is optional, consider it as required
* @default false
Expand Down
151 changes: 151 additions & 0 deletions src/__tests__/doublequotesEscape/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { existsSync, readFileSync, rmdirSync } from 'fs';

import { convertFromDirectory } from '../../index';

describe('Use double quotes for string escapes', () => {
const typeOutputDirectory = './src/__tests__/doublequotesEscape/interfaces';

test('default behavior / single quotes', async () => {
if (existsSync(typeOutputDirectory)) {
rmdirSync(typeOutputDirectory, { recursive: true });
}
const result = await convertFromDirectory({
schemaDirectory: './src/__tests__/doublequotesEscape/schemas',
typeOutputDirectory
});

expect(result).toBe(true);

const readmeContent = readFileSync(`${typeOutputDirectory}/Allow.ts`).toString();

expect(readmeContent).toBe(`/**
* This file was automatically generated by joi-to-typescript
* Do not modify this file manually
*/
export type Blank = string;
export type BlankNull = string | null | '';
/**
* This is date
*/
export type DateOptions = Date | null;
/**
* Test Schema Name
*/
export type Name = string;
export type NormalList = 'red' | 'green' | 'blue';
export type NormalRequiredList = 'red' | 'green' | 'blue';
/**
* nullable
*/
export type NullName = string | null;
export type NullNumber = number | null;
export type Numbers = 1 | 2 | 3 | 4 | 5;
`);
});

test('doublequoteEscape: false / single quotes', async () => {
if (existsSync(typeOutputDirectory)) {
rmdirSync(typeOutputDirectory, { recursive: true });
}
const result = await convertFromDirectory({
schemaDirectory: './src/__tests__/doublequotesEscape/schemas',
typeOutputDirectory,
doublequoteEscape: false,
});

expect(result).toBe(true);

const readmeContent = readFileSync(`${typeOutputDirectory}/Allow.ts`).toString();

expect(readmeContent).toBe(`/**
* This file was automatically generated by joi-to-typescript
* Do not modify this file manually
*/
export type Blank = string;
export type BlankNull = string | null | '';
/**
* This is date
*/
export type DateOptions = Date | null;
/**
* Test Schema Name
*/
export type Name = string;
export type NormalList = 'red' | 'green' | 'blue';
export type NormalRequiredList = 'red' | 'green' | 'blue';
/**
* nullable
*/
export type NullName = string | null;
export type NullNumber = number | null;
export type Numbers = 1 | 2 | 3 | 4 | 5;
`);
});

test('doublequoteEscape: true / double quotes', async () => {
if (existsSync(typeOutputDirectory)) {
rmdirSync(typeOutputDirectory, { recursive: true });
}
const result = await convertFromDirectory({
schemaDirectory: './src/__tests__/doublequotesEscape/schemas',
typeOutputDirectory,
doublequoteEscape: true,
});

expect(result).toBe(true);

const readmeContent = readFileSync(`${typeOutputDirectory}/Allow.ts`).toString();

expect(readmeContent).toBe(`/**
* This file was automatically generated by joi-to-typescript
* Do not modify this file manually
*/
export type Blank = string;
export type BlankNull = string | null | "";
/**
* This is date
*/
export type DateOptions = Date | null;
/**
* Test Schema Name
*/
export type Name = string;
export type NormalList = "red" | "green" | "blue";
export type NormalRequiredList = "red" | "green" | "blue";
/**
* nullable
*/
export type NullName = string | null;
export type NullNumber = number | null;
export type Numbers = 1 | 2 | 3 | 4 | 5;
`);
});

});
11 changes: 11 additions & 0 deletions src/__tests__/doublequotesEscape/schemas/AllowSchema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Joi from 'joi';

export const Name = Joi.string().optional().description('Test Schema Name').allow('').meta({ className: 'Name' });
export const NullName = Joi.string().optional().description('nullable').allow(null);
export const BlankNull = Joi.string().optional().allow(null, '');
export const Blank = Joi.string().allow('');
export const NormalList = Joi.string().allow('red', 'green', 'blue');
export const NormalRequiredList = Joi.string().allow('red', 'green', 'blue').required();
export const Numbers = Joi.number().optional().allow(1, 2, 3, 4, 5);
export const NullNumber = Joi.number().optional().allow(null);
export const DateOptions = Joi.date().allow(null).description('This is date');
18 changes: 9 additions & 9 deletions src/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ export function parseSchema(
if (interfaceOrTypeName && useLabels && !ignoreLabels.includes(interfaceOrTypeName)) {
// skip parsing and just reference the label since we assumed we parsed the schema that the label references
// TODO: do we want to use the labels description if we reference it?
let allowedValues = createAllowTypes(details);
let allowedValues = createAllowTypes(details, settings);

const child = makeTypeContentChild({
content: interfaceOrTypeName,
Expand Down Expand Up @@ -509,7 +509,7 @@ function parseBasicSchema(details: BasicDescribe, settings: Settings, rootSchema

// at least one value
if (values.length !== 0) {
const allowedValues = createAllowTypes(details);
const allowedValues = createAllowTypes(details, settings);

if (values[0] === null && !details.flags?.only) {
allowedValues.unshift(makeTypeContentChild({ content }));
Expand All @@ -529,13 +529,13 @@ function parseBasicSchema(details: BasicDescribe, settings: Settings, rootSchema
}
}

function createAllowTypes(details: BaseDescribe): TypeContent[] {
function createAllowTypes(details: BaseDescribe, settings: Settings): TypeContent[] {
const values = getAllowValues(details.allow);

// at least one value
if (values.length !== 0) {
const allowedValues = values.map((value: unknown) =>
makeTypeContentChild({ content: typeof value === 'string' ? toStringLiteral(value) : `${value}` })
makeTypeContentChild({ content: typeof value === 'string' ? toStringLiteral(value, settings.doublequoteEscape) : `${value}` })
);
return allowedValues;
}
Expand All @@ -560,7 +560,7 @@ function parseStringSchema(details: StringDescribe, settings: Settings, rootSche
const allowedValues = values.map(value =>
stringAllowValues.includes(value) && value !== ''
? makeTypeContentChild({ content: `${value}` })
: makeTypeContentChild({ content: toStringLiteral(value) })
: makeTypeContentChild({ content: toStringLiteral(value, settings.doublequoteEscape) })
);

if (values.filter(value => stringAllowValues.includes(value)).length === values.length) {
Expand Down Expand Up @@ -588,7 +588,7 @@ function parseArray(details: ArrayDescribe, settings: Settings): TypeContent | u

if (details.ordered && !details.items) {
const parsedChildren = details.ordered.map(item => parseSchema(item, settings)).filter(Boolean) as TypeContent[];
const allowedValues = createAllowTypes(details);
const allowedValues = createAllowTypes(details, settings);

// at least one value
if (allowedValues.length > 0) {
Expand Down Expand Up @@ -618,7 +618,7 @@ function parseArray(details: ArrayDescribe, settings: Settings): TypeContent | u
return undefined;
}

const allowedValues = createAllowTypes(details);
const allowedValues = createAllowTypes(details, settings);
// at least one value
if (allowedValues.length !== 0) {
allowedValues.unshift(
Expand Down Expand Up @@ -675,7 +675,7 @@ function parseAlternatives(details: AlternativesDescribe, settings: Settings): T
return undefined;
}

const allowedValues = createAllowTypes(details);
const allowedValues = createAllowTypes(details, settings);

return makeTypeContentRoot({
joinOperation: 'union',
Expand Down Expand Up @@ -765,7 +765,7 @@ function parseObjects(details: ObjectDescribe, settings: Settings): TypeContent

const { interfaceOrTypeName, jsDoc } = getCommonDetails(details, settings);

const allowedValues = createAllowTypes(details);
const allowedValues = createAllowTypes(details, settings);

// at least one value
if (allowedValues.length !== 0) {
Expand Down
5 changes: 5 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ export interface Settings {
* @default ' ' (two spaces)
*/
readonly indentationChacters: string;
/**
* If set to true, will use double quotes for strings
* @default false
*/
readonly doublequoteEscape: boolean;
/**
* If a field has a default and is optional, consider it as required
* @default false
Expand Down
13 changes: 11 additions & 2 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,17 @@ export function filterMap<T, K>(list: T[], mapper: (t: T) => K | undefined): K[]
* Escape value so that it can be go into single quoted string literal.
* @param value
*/
export function toStringLiteral(value?: string): string {
return `'${value?.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
export function toStringLiteral(value: string, doublequoteEscape: boolean): string {
const escapeChar = doublequoteEscape ? '"' : "'";

value = value.replace(/\\/g, '\\\\');
if (doublequoteEscape) {
value = value.replace(/"/g, '\\"');
} else {
value = value.replace(/'/g, "\\'");
}

return `${escapeChar}${value}${escapeChar}`;
}

export function isDescribe(x: unknown): x is Describe {
Expand Down

0 comments on commit efc9b8c

Please sign in to comment.