Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions code/frameworks/angular/src/client/compodoc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ describe('extractType', () => {
// ['T[]', { name: 'other', value: 'empty-enum' }], // seems to be wrong | TODO: REVISIT
['[]', { name: 'other', value: 'empty-enum' }],
['"primary" | "secondary"', { name: 'enum', value: ['primary', 'secondary'] }],
["'primary' | 'secondary'", { name: 'enum', value: ['primary', 'secondary'] }],
["'S' | 'M' | 'L'", { name: 'enum', value: ['S', 'M', 'L'] }],
['TypeAlias', { name: 'enum', value: ['Type Alias 1', 'Type Alias 2', 'Type Alias 3'] }],
// ['EnumNumeric', { name: 'other', value: 'empty-enum' }], // seems to be wrong | TODO: REVISIT
// ['EnumNumericInitial', { name: 'other', value: 'empty-enum' }], // seems to be wrong | TODO: REVISIT
Expand Down
22 changes: 16 additions & 6 deletions code/frameworks/angular/src/client/compodoc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,19 +122,29 @@ const extractEnumValues = (compodocType: any) => {
const compodocJson = getCompodocJson();
const enumType = compodocJson?.miscellaneous?.enumerations?.find((x) => x.name === compodocType);

if (enumType?.childs.every((x) => x.value)) {
if (enumType?.childs.every((x) => x.value != null)) {
return enumType.childs.map((x) => x.value);
}

if (typeof compodocType !== 'string' || compodocType.indexOf('|') === -1) {
return null;
}

try {
return compodocType.split('|').map((value) => JSON.parse(value));
} catch (e) {
return null;
}
const values = compodocType.split('|').map((segment): unknown => {
const s = segment.trim();
// Compodoc emits TypeScript literal unions with single-quoted strings (e.g. 'S' | 'M' | 'L'),
// which JSON.parse rejects. Strip the surrounding quotes and unescape inner escaped quotes.
if (s.length >= 2 && s[0] === "'" && s[s.length - 1] === "'") {
return s.slice(1, -1).replace(/\\'/g, "'");
}
try {
return JSON.parse(s);
} catch {
return undefined;
}
});

return values.some((v) => v === undefined) ? null : values;
Comment on lines +133 to +147

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In JavaScript/TypeScript single-quoted string literals, are escape sequences such as \n, \t, \\, \", \', and \uXXXX interpreted the same as in double-quoted strings, and what runtime string values do they produce?

💡 Result:

In JavaScript and TypeScript, single-quoted ('...') and double-quoted ("...") string literals are semantically identical regarding escape sequences [1][2]. All standard escape sequences—including \n, \t, \, ", ', and \uXXXX—are interpreted and processed in exactly the same way regardless of whether the string is wrapped in single or double quotes [3][4][2]. When these escape sequences are used, they produce the following runtime string values: \n: Line Feed (LF) character (U+000A) [3][4] \t: Character Tabulation (horizontal tab) (U+0009) [3][4] \: Backslash (Reverse Solidus) (U+005C) [3][4] ": Quotation Mark (U+0022) [3][4] ': Apostrophe (U+0027) [3][4] \uXXXX: A character represented by the hexadecimal Unicode code point XXXX (e.g., \u0041 produces 'A') [3][5] Because both quote types are treated identically, the only functional difference is which character must be escaped to avoid prematurely terminating the string: you must escape the quote character used to define the literal [4][2]. For example, in a single-quoted string, a literal single quote must be escaped as ', while a double quote can appear literally [2]. Conversely, in a double-quoted string, a literal double quote must be escaped as ", while a single quote can appear literally [2]. All other escape sequences remain fully interchangeable [2].

Citations:


Decode all valid escape sequences in the single-quoted enum segment parser (lines 133-147 in code/frameworks/angular/src/client/compodoc.ts)
The current single-quoted branch only unescapes \' (s.slice(1, -1).replace(/\\'/g, "'")) and leaves other valid TypeScript/JavaScript string escapes (e.g. \n, \t, \\, \", \uXXXX) undecoded, so inferred enum option values can diverge from the actual runtime strings.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@code/frameworks/angular/src/client/compodoc.ts` around lines 133 - 147, The
single-quoted branch in the compodocType parser (inside the values mapping) only
unescapes \', but must decode all JavaScript escape sequences; replace the
simple replace with logic that takes the inner content (s.slice(1, -1)), safely
constructs a JSON string literal by escaping backslashes and double quotes, then
call JSON.parse on '"' + innerEscaped + '"' to decode sequences (so use
JSON.parse to produce the final decoded string instead of only replacing
/\\'/g), returning that decoded value in the values array and keeping the
existing fallback to JSON.parse for non-quoted segments; update the code where
compodocType and values are handled to use this decoding path.

};

export const extractType = (property: Property, defaultValue: any): SBType => {
Expand Down