Skip to content

Commit

Permalink
fix: enum name consisting of symbols lead to conflicting enum values (#…
Browse files Browse the repository at this point in the history
…1261)

Characters like, `!=, ==, =~, !~` etc would result in `VALUE_` enum
values since we are not able to code generate these characters to
something valid. This PR is adding an allowlist with some known
characters to provide valid enum values if such a case occurs.

Fixes cdk8s-team/cdk8s-cli#578

---------

Signed-off-by: Vinayak Kukreja <vinakuk@amazon.com>
  • Loading branch information
vinayak-kukreja committed Dec 8, 2023
1 parent 9b125cc commit 1285288
Show file tree
Hide file tree
Showing 4 changed files with 219 additions and 4 deletions.
39 changes: 39 additions & 0 deletions src/allowlist.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
export const NAMED_SYMBOLS = {
EXCLAMATION: '!',
AT_SIGN: '@',
BACKTICK: '`',
AMPERSAND: '&',
APOSTROPHE: '\'',
ASTERISK: '*',
HYPHEN: '-',
UNDERSCORE: '_',
DOLLAR_SIGN: '$',
POUND_SIGN: '#',
TILDE: '~',
PERCENT: '%',
CARAT: '^',
PIPE: '|',
QUESTION_MARK: '?',
COMMA: ',',
PERIOD: '.',
LESS_THAN: '<',
GREATER_THAN: '>',
OPEN_BRACKET: '(',
CLOSE_BRACKET: ')',
OPEN_BRACE: '{',
CLOSE_BRACE: '}',
SEMI_COLON: ';',
QUOTATION_MARK: '"',
FORWARD_SLASH: '/',
BACKWARD_SLASH: '\\',
NOT_EQUALS_TO: '!=',
EQUALS_TO: '==',
DEEP_EQUALS: '===',
EQUAL_TILDE: '=~',
NEGATION_TILDE: '!~',
ROUND_BRACKETS: '()',
CURLY_BRACES: '{}',
ANGLE_BRACKETS: '<>',
GREATER_THAN_EQUAL_TO: '>=',
LESSER_THAN_EQUAL_TO: '<=',
};
64 changes: 62 additions & 2 deletions src/type-generator.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import camelCase from 'camelcase';
import { JSONSchema4 } from 'json-schema';
import { snakeCase } from 'snake-case';
import { NAMED_SYMBOLS } from './allowlist';
import { Code } from './code';
import { ToJsonFunction } from './tojson';

Expand Down Expand Up @@ -528,8 +529,7 @@ export class TypeGenerator {
throw new Error('only enums with string or number values are supported');
}

// sluggify and turn to UPPER_SNAKE_CASE
let memberName = snakeCase(`${value}`.replace(/[^a-z0-9]/gi, '_')).split('_').filter(x => x).join('_').toUpperCase();
let memberName = snakeCase(rewriteNamedSymbols(`${value}`).replace(/[^a-z0-9]/gi, '_')).split('_').filter(x => x).join('_').toUpperCase();

// If enums of same value exists, then we choose one of them and skip adding others
// since that would cause conflict
Expand Down Expand Up @@ -673,5 +673,65 @@ interface EmittedType {
readonly toJson: (code: string) => string;
}

function rewriteNamedSymbols(input: string): string {
const ret = new Array<string>();

let cursor = 0;
while (cursor < input.length) {
const [prefixName, prefixLen] = longestPrefixMatch(input, cursor, NAMED_SYMBOLS);
if (prefixName) {
const prefix = `_${prefixName}_`.split('');
ret.push(...prefix);
cursor += prefixLen;
} else {
ret.push(input.charAt(cursor));
cursor += 1;
}
}

// Remove underscores if its only prefix to be returned
if (ret[0] === '_') { ret.unshift('VALUE'); }
if (ret[ret.length - 1] === '_') { ret.pop(); }

return ret.join('');
}

function longestPrefixMatch(input: string, index: number, lookupTable: Record<string, string>): [string | undefined, number] {
let ret: string | undefined;
let longest: number = 0;

for (const [name, value] of Object.entries(lookupTable)) {
if (hasSubStringAt(input, index, value) && value.length > longest && !isExemptPattern(input, index)) {
ret = name;
longest = value.length;
}
}
return [ret, longest];
}

function hasSubStringAt(input: string, index: number, substring: string): boolean {
if (index == input.indexOf(substring, index)) {
return true;
}

return false;
}

function isExemptPattern(input: string, index: number): boolean {
const exemptPatterns = [
// 9.9, 9.
/(?<=\d)\.\d/,
];

return exemptPatterns.some((p) => testRegexAt(p, input, index));
}

function testRegexAt(regex: RegExp, input: string, index: number): boolean {
const re = new RegExp(regex, 'y');
re.lastIndex = index;

return re.test(input);
}

type TypeEmitter = (code: Code) => EmittedType;
type CodeEmitter = (code: Code) => void;
86 changes: 84 additions & 2 deletions test/__snapshots__/type-generator.test.ts.snap

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 34 additions & 0 deletions test/type-generator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,40 @@ describe('enums', () => {
},
});

which('has allowlisted characters', {
properties: {
Chars: {
type: 'string',
enum: [
'!=',
'!',
'<=',
'!~',
'\'',
'\\',
'Foo.Bar!=',
'Foo.!=Bar',
'bool:true',
'Foo-Bar',
'Foo_Bar',
'Foo.Bar',
'0.Foo',
'Foo.0',
'.Foo',
'Foo.',
'0.9',
'.9',
'9',
'9.',
'9.9',
'99',
'9.90.0.9',
'Foo<>>=9.0.9{}-Bar',
],
},
},
});

which('has repeated values', {
properties: {
Same: {
Expand Down

0 comments on commit 1285288

Please sign in to comment.