diff --git a/extensions/ql-vscode/CHANGELOG.md b/extensions/ql-vscode/CHANGELOG.md
index 6ccc6abf46e..6bd2a2ca832 100644
--- a/extensions/ql-vscode/CHANGELOG.md
+++ b/extensions/ql-vscode/CHANGELOG.md
@@ -2,6 +2,8 @@
## [UNRELEASED]
+- Replace certain control codes (`U+0000` - `U+001F`) with their corresponding control labels (`U+2400` - `U+241F`) in the results view. [#963](https://github.com/github/vscode-codeql/pull/963)
+
## 1.5.6 - 07 October 2021
- Add progress messages to LGTM download option. This makes the two-step process (selecting a project, then selecting a language) more clear. [#960](https://github.com/github/vscode-codeql/pull/960)
diff --git a/extensions/ql-vscode/src/view/RawTableValue.tsx b/extensions/ql-vscode/src/view/RawTableValue.tsx
index 013715c1531..5f3ee9fa7f4 100644
--- a/extensions/ql-vscode/src/view/RawTableValue.tsx
+++ b/extensions/ql-vscode/src/view/RawTableValue.tsx
@@ -9,14 +9,14 @@ interface Props {
}
export default function RawTableValue(props: Props): JSX.Element {
- const v = props.value;
+ const rawValue = props.value;
if (
- typeof v === 'string'
- || typeof v === 'number'
- || typeof v === 'boolean'
+ typeof rawValue === 'string'
+ || typeof rawValue === 'number'
+ || typeof rawValue === 'boolean'
) {
- return {v.toString()};
+ return {renderLocation(undefined, rawValue.toString())};
}
- return renderLocation(v.url, v.label, props.databaseUri);
+ return renderLocation(rawValue.url, rawValue.label, props.databaseUri);
}
diff --git a/extensions/ql-vscode/src/view/result-table-utils.tsx b/extensions/ql-vscode/src/view/result-table-utils.tsx
index 64bb10564f7..a96edd3e051 100644
--- a/extensions/ql-vscode/src/view/result-table-utils.tsx
+++ b/extensions/ql-vscode/src/view/result-table-utils.tsx
@@ -37,6 +37,9 @@ export const oddRowClassName = 'vscode-codeql__result-table-row--odd';
export const pathRowClassName = 'vscode-codeql__result-table-row--path';
export const selectedRowClassName = 'vscode-codeql__result-table-row--selected';
+const CONTROL_CODE = '\u001F'.codePointAt(0)!;
+const CONTROL_LABEL = '\u2400'.codePointAt(0)!;
+
export function jumpToLocationHandler(
loc: ResolvableLocationValue,
databaseUri: string,
@@ -67,24 +70,42 @@ export function openFile(filePath: string): void {
});
}
+function convertedNonprintableChars(label: string) {
+ // If the label was empty, use a placeholder instead, so the link is still clickable.
+ if (!label) {
+ return '[empty string]';
+ } else if (label.match(/^\s+$/)) {
+ return `[whitespace: "${label}"]`;
+ } else {
+ /**
+ * If the label contains certain non-printable characters, loop through each
+ * character and replace it with the cooresponding unicode control label.
+ */
+ const convertedLabelArray: any[] = [];
+ for (let i = 0; i < label.length; i++) {
+ const labelCheck = label.codePointAt(i)!;
+ if (labelCheck <= CONTROL_CODE) {
+ convertedLabelArray[i] = String.fromCodePoint(labelCheck + CONTROL_LABEL);
+ } else {
+ convertedLabelArray[i] = label.charAt(i);
+ }
+ }
+ return convertedLabelArray.join('');
+ }
+}
+
/**
* Render a location as a link which when clicked displays the original location.
*/
export function renderLocation(
- loc: UrlValue | undefined,
- label: string | undefined,
- databaseUri: string,
+ loc?: UrlValue,
+ label?: string,
+ databaseUri?: string,
title?: string,
callback?: () => void
): JSX.Element {
- // If the label was empty, use a placeholder instead, so the link is still clickable.
- let displayLabel = label;
- if (!label) {
- displayLabel = '[empty string]';
- } else if (label.match(/^\s+$/)) {
- displayLabel = `[whitespace: "${label}"]`;
- }
+ const displayLabel = convertedNonprintableChars(label!);
if (loc === undefined) {
return {displayLabel};
@@ -93,7 +114,7 @@ export function renderLocation(
}
const resolvableLoc = tryGetResolvableLocation(loc);
- if (resolvableLoc !== undefined) {
+ if (databaseUri !== undefined && resolvableLoc !== undefined) {
return (