Commit f7ad1e4c authored by Kushal Pandya's avatar Kushal Pandya

Merge branch '341837-highlight-bidi-chars' into 'master'

Highlight BiDi chars on the frontend

See merge request gitlab-org/gitlab!83924
parents a8999822 9d1ca9cd
<script> <script>
import { GlLink, GlSafeHtmlDirective } from '@gitlab/ui'; import { GlLink, GlSafeHtmlDirective } from '@gitlab/ui';
import { setAttributes } from '~/lib/utils/dom_utils';
import { BIDI_CHARS, BIDI_CHARS_CLASS_LIST, BIDI_CHAR_TOOLTIP } from '../constants';
export default { export default {
components: { components: {
...@@ -22,6 +24,34 @@ export default { ...@@ -22,6 +24,34 @@ export default {
required: true, required: true,
}, },
}, },
computed: {
formattedContent() {
let { content } = this;
BIDI_CHARS.forEach((bidiChar) => {
if (content.includes(bidiChar)) {
content = content.replace(bidiChar, this.wrapBidiChar(bidiChar));
}
});
return content;
},
},
methods: {
wrapBidiChar(bidiChar) {
const span = document.createElement('span');
setAttributes(span, {
class: BIDI_CHARS_CLASS_LIST,
title: BIDI_CHAR_TOOLTIP,
'data-testid': 'bidi-wrapper',
});
span.innerText = bidiChar;
return span.outerHTML;
},
},
}; };
</script> </script>
<template> <template>
...@@ -39,6 +69,6 @@ export default { ...@@ -39,6 +69,6 @@ export default {
<pre <pre
class="code highlight gl-p-0! gl-w-full gl-overflow-visible! gl-ml-11!" class="code highlight gl-p-0! gl-w-full gl-overflow-visible! gl-ml-11!"
><code><span :id="`LC${number}`" v-safe-html="content" :lang="language" class="line" data-testid="content"></span></code></pre> ><code><span :id="`LC${number}`" v-safe-html="formattedContent" :lang="language" class="line" data-testid="content"></span></code></pre>
</div> </div>
</template> </template>
import { __ } from '~/locale';
// Language map from Rouge::Lexer to highlight.js // Language map from Rouge::Lexer to highlight.js
// Rouge::Lexer - We use it on the BE to determine the language of a source file (https://github.com/rouge-ruby/rouge/blob/master/docs/Languages.md). // Rouge::Lexer - We use it on the BE to determine the language of a source file (https://github.com/rouge-ruby/rouge/blob/master/docs/Languages.md).
// Highlight.js - We use it on the FE to highlight the syntax of a source file (https://github.com/highlightjs/highlight.js/tree/main/src/languages). // Highlight.js - We use it on the FE to highlight the syntax of a source file (https://github.com/highlightjs/highlight.js/tree/main/src/languages).
...@@ -111,3 +113,24 @@ export const ROUGE_TO_HLJS_LANGUAGE_MAP = { ...@@ -111,3 +113,24 @@ export const ROUGE_TO_HLJS_LANGUAGE_MAP = {
}; };
export const LINES_PER_CHUNK = 70; export const LINES_PER_CHUNK = 70;
export const BIDI_CHARS = [
'\u202A', // Left-to-Right Embedding (Try treating following text as left-to-right)
'\u202B', // Right-to-Left Embedding (Try treating following text as right-to-left)
'\u202D', // Left-to-Right Override (Force treating following text as left-to-right)
'\u202E', // Right-to-Left Override (Force treating following text as right-to-left)
'\u2066', // Left-to-Right Isolate (Force treating following text as left-to-right without affecting adjacent text)
'\u2067', // Right-to-Left Isolate (Force treating following text as right-to-left without affecting adjacent text)
'\u2068', // First Strong Isolate (Force treating following text in direction indicated by the next character)
'\u202C', // Pop Directional Formatting (Terminate nearest LRE, RLE, LRO, or RLO)
'\u2069', // Pop Directional Isolate (Terminate nearest LRI or RLI)
'\u061C', // Arabic Letter Mark (Right-to-left zero-width Arabic character)
'\u200F', // Right-to-Left Mark (Right-to-left zero-width character non-Arabic character)
'\u200E', // Left-to-Right Mark (Left-to-right zero-width character)
];
export const BIDI_CHARS_CLASS_LIST = 'unicode-bidi has-tooltip';
export const BIDI_CHAR_TOOLTIP = __(
'Potentially unwanted character detected: Unicode BiDi Control',
);
import { GlLink } from '@gitlab/ui'; import { GlLink } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import ChunkLine from '~/vue_shared/components/source_viewer/components/chunk_line.vue'; import ChunkLine from '~/vue_shared/components/source_viewer/components/chunk_line.vue';
import {
BIDI_CHARS,
BIDI_CHARS_CLASS_LIST,
BIDI_CHAR_TOOLTIP,
} from '~/vue_shared/components/source_viewer/constants';
const DEFAULT_PROPS = { const DEFAULT_PROPS = {
number: 2, number: 2,
...@@ -17,6 +22,7 @@ describe('Chunk Line component', () => { ...@@ -17,6 +22,7 @@ describe('Chunk Line component', () => {
const findLink = () => wrapper.findComponent(GlLink); const findLink = () => wrapper.findComponent(GlLink);
const findContent = () => wrapper.findByTestId('content'); const findContent = () => wrapper.findByTestId('content');
const findWrappedBidiChars = () => wrapper.findAllByTestId('bidi-wrapper');
beforeEach(() => { beforeEach(() => {
createComponent(); createComponent();
...@@ -25,6 +31,22 @@ describe('Chunk Line component', () => { ...@@ -25,6 +31,22 @@ describe('Chunk Line component', () => {
afterEach(() => wrapper.destroy()); afterEach(() => wrapper.destroy());
describe('rendering', () => { describe('rendering', () => {
it('wraps BiDi characters', () => {
const content = `// some content ${BIDI_CHARS.toString()} with BiDi chars`;
createComponent({ content });
const wrappedBidiChars = findWrappedBidiChars();
expect(wrappedBidiChars.length).toBe(BIDI_CHARS.length);
wrappedBidiChars.wrappers.forEach((_, i) => {
expect(wrappedBidiChars.at(i).text()).toBe(BIDI_CHARS[i]);
expect(wrappedBidiChars.at(i).attributes()).toMatchObject({
class: BIDI_CHARS_CLASS_LIST,
title: BIDI_CHAR_TOOLTIP,
});
});
});
it('renders a line number', () => { it('renders a line number', () => {
expect(findLink().attributes()).toMatchObject({ expect(findLink().attributes()).toMatchObject({
'data-line-number': `${DEFAULT_PROPS.number}`, 'data-line-number': `${DEFAULT_PROPS.number}`,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment