Skip to content

Commit a205111

Browse files
committed
Support non-ascii (unicode) enum and type names.
This replaces regexp patterns that only worked with ascii characters with more proper matching that supports unicode identifiers in typescript/javascript. The platform must support "unicode-aware mode" (the u flag) for this to work. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/unicode
1 parent 42a51ec commit a205111

File tree

14 files changed

+193
-22
lines changed

14 files changed

+193
-22
lines changed

src/openApi/v2/parser/escapeName.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import validTypescriptIdentifierRegex from '../../../utils/validTypescriptIdentifierRegex';
2+
13
export const escapeName = (value: string): string => {
24
if (value || value === '') {
3-
const validName = /^[a-zA-Z_$][\w$]+$/g.test(value);
5+
const validName = validTypescriptIdentifierRegex.test(value);
46
if (!validName) {
57
return `'${value}'`;
68
}

src/openApi/v2/parser/getEnum.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { Enum } from '../../../client/interfaces/Enum';
2+
import sanitizeEnumName from '../../../utils/sanitizeEnumName';
23

34
export const getEnum = (values?: (string | number)[]): Enum[] => {
45
if (Array.isArray(values)) {
@@ -19,11 +20,7 @@ export const getEnum = (values?: (string | number)[]): Enum[] => {
1920
};
2021
}
2122
return {
22-
name: String(value)
23-
.replace(/\W+/g, '_')
24-
.replace(/^(\d+)/g, '_$1')
25-
.replace(/([a-z])([A-Z]+)/g, '$1_$2')
26-
.toUpperCase(),
23+
name: sanitizeEnumName(String(value)),
2724
value: `'${value.replace(/'/g, "\\'")}'`,
2825
type: 'string',
2926
description: null,

src/openApi/v2/parser/getType.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import type { Type } from '../../../client/interfaces/Type';
2+
import sanitizeTypeName from '../../../utils/sanitizeTypeName';
23
import { getMappedType } from './getMappedType';
34
import { stripNamespace } from './stripNamespace';
45

5-
const encode = (value: string): string => {
6-
return value.replace(/^[^a-zA-Z_$]+/g, '').replace(/[^\w$]+/g, '_');
7-
};
6+
const encode = (value: string): string => sanitizeTypeName(value);
87

98
/**
109
* Parse any string value into a type object.

src/openApi/v3/parser/escapeName.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import validTypescriptIdentifierRegex from '../../../utils/validTypescriptIdentifierRegex';
2+
13
export const escapeName = (value: string): string => {
24
if (value || value === '') {
3-
const validName = /^[a-zA-Z_$][\w$]+$/g.test(value);
5+
const validName = validTypescriptIdentifierRegex.test(value);
46
if (!validName) {
57
return `'${value}'`;
68
}

src/openApi/v3/parser/getEnum.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { Enum } from '../../../client/interfaces/Enum';
2+
import sanitizeEnumName from '../../../utils/sanitizeEnumName';
23

34
export const getEnum = (values?: (string | number)[]): Enum[] => {
45
if (Array.isArray(values)) {
@@ -19,11 +20,7 @@ export const getEnum = (values?: (string | number)[]): Enum[] => {
1920
};
2021
}
2122
return {
22-
name: String(value)
23-
.replace(/\W+/g, '_')
24-
.replace(/^(\d+)/g, '_$1')
25-
.replace(/([a-z])([A-Z]+)/g, '$1_$2')
26-
.toUpperCase(),
23+
name: sanitizeEnumName(String(value)),
2724
value: `'${value.replace(/'/g, "\\'")}'`,
2825
type: 'string',
2926
description: null,

src/openApi/v3/parser/getType.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import type { Type } from '../../../client/interfaces/Type';
22
import { isDefined } from '../../../utils/isDefined';
3+
import sanitizeTypeName from '../../../utils/sanitizeTypeName';
34
import { getMappedType } from './getMappedType';
45
import { stripNamespace } from './stripNamespace';
56

6-
const encode = (value: string): string => {
7-
return value.replace(/^[^a-zA-Z_$]+/g, '').replace(/[^\w$]+/g, '_');
8-
};
7+
const encode = (value: string): string => sanitizeTypeName(value);
98

109
/**
1110
* Parse any string value into a type object.

src/utils/sanitizeEnumName.spec.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import sanitizeEnumName from './sanitizeEnumName';
2+
3+
describe('sanitizeEnumName', () => {
4+
it('should replace illegal characters', () => {
5+
expect(sanitizeEnumName('abc')).toEqual('ABC');
6+
expect(sanitizeEnumName('æbc')).toEqual('ÆBC');
7+
expect(sanitizeEnumName('æb.c')).toEqual('ÆB_C');
8+
expect(sanitizeEnumName('1æb.c')).toEqual('_1ÆB_C');
9+
expect(sanitizeEnumName("'quoted'")).toEqual('_QUOTED_');
10+
});
11+
});

src/utils/sanitizeEnumName.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* Sanitizes names of enums, so they are valid typescript identifiers of a certain form.
3+
*
4+
* 1: Replace all characters not legal as part of identifier with '_'
5+
* 2: Add '_' prefix if first character of enum name has character not legal for start of identifier
6+
* 3: Add '_' where the string transitions from lowercase to uppercase
7+
* 4: Transform the whole string to uppercase
8+
*
9+
* Javascript identifier regexp pattern retrieved from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#identifiers
10+
*/
11+
const sanitizeEnumName = (name: string) =>
12+
name
13+
.replace(/[^$\u200c\u200d\p{ID_Continue}]/gu, '_')
14+
.replace(/^([^$_\p{ID_Start}])/u, '_$1')
15+
.replace(/(\p{Lowercase})(\p{Uppercase}+)/gu, '$1_$2')
16+
.toUpperCase();
17+
18+
export default sanitizeEnumName;

src/utils/sanitizeTypeName.spec.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import sanitizeTypeName from './sanitizeTypeName';
2+
3+
describe('sanitizeTypeName', () => {
4+
it('should remove/replace illegal characters', () => {
5+
expect(sanitizeTypeName('abc')).toEqual('abc');
6+
expect(sanitizeTypeName('æbc')).toEqual('æbc');
7+
expect(sanitizeTypeName('æb.c')).toEqual('æb_c');
8+
expect(sanitizeTypeName('1æb.c')).toEqual('æb_c');
9+
});
10+
});

src/utils/sanitizeTypeName.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/**
2+
* Sanitizes names of types, so they are valid typescript identifiers of a certain form.
3+
*
4+
* 1: Remove any leading characters that are illegal as starting character of a typescript identifier.
5+
* 2: Replace illegal characters in remaining part of type name with underscore (_).
6+
*
7+
* Step 1 should perhaps instead also replace illegal characters with underscore, or prefix with it, like sanitizeEnumName
8+
* does. The way this is now one could perhaps end up removing all characters, if all are illegal start characters. It
9+
* would be sort of a breaking change to do so, though, previously generated code might change then.
10+
*
11+
* Javascript identifier regexp pattern retrieved from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#identifiers
12+
*/
13+
const sanitizeTypeName = (name: string) =>
14+
name.replace(/^[^$_\p{ID_Start}]+/u, '').replace(/[^$\u200c\u200d\p{ID_Continue}]/gu, '_');
15+
16+
export default sanitizeTypeName;

0 commit comments

Comments
 (0)