Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 | 1x 18x 18x 18x 2x 2x 4x 4x 2x 2x 2x 16x 1x 1x 2x 2x 1x 1x 1x 15x 1x 1x 3x 3x 1x 2x 2x 18x 1x 20x 20x 2x 2x 1x 1x 1x 20x 18x 18x 20x 5x | import React, { useState, useMemo } from "react";
import { CodeBlockProps } from "../../types/componentPropExports";
/**
* Simple syntax highlighting for common tokens
* No external libraries - just basic regex-based highlighting
*
* Uses single-pass tokenizer approach to avoid matching keywords inside
* comments or strings. The combined regex matches all patterns at once,
* and the replacement function decides how to highlight each match.
*/
const highlightCode = (code: string, language?: string): string => {
let highlighted = code;
// Escape HTML
highlighted = highlighted
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">");
// Apply syntax highlighting based on language using single-pass tokenizer
if (language === "typescript" || language === "javascript" || language === "jsx" || language === "tsx") {
// Combined pattern matches comments, strings, keywords, and numbers in one pass
const tsPattern = /(\/\/.*$|\/\*[\s\S]*?\*\/)|("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`)|(\b(?:const|let|var|function|return|if|else|for|while|class|interface|type|import|export|from|default|async|await|try|catch|throw|new|this|extends|implements|public|private|protected|static|readonly)\b)|(\b\d+\b)/gm;
highlighted = highlighted.replace(
tsPattern,
(match: string, comment: string, str: string, keyword: string, num: string): string => {
Iif (comment) {
return `<span class="text-gray-500 dark:text-gray-400 italic">${comment}</span>`;
}
if (str) {
return `<span class="text-green-600 dark:text-green-400">${str}</span>`;
}
Eif (keyword) {
return `<span class="text-purple-600 dark:text-purple-400">${keyword}</span>`;
}
if (num) {
return `<span class="text-blue-600 dark:text-blue-400">${num}</span>`;
}
return match;
}
);
} else if (language === "python") {
// Combined pattern for Python
const pythonPattern = /(#.*$)|("""[\s\S]*?"""|'''[\s\S]*?'''|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')|(\b(?:def|class|if|elif|else|for|while|return|import|from|as|try|except|finally|with|lambda|yield|raise|pass|break|continue|True|False|None)\b)/gm;
highlighted = highlighted.replace(
pythonPattern,
(match: string, comment: string, str: string, keyword: string): string => {
Iif (comment) {
return `<span class="text-gray-500 dark:text-gray-400 italic">${comment}</span>`;
}
if (str) {
return `<span class="text-green-600 dark:text-green-400">${str}</span>`;
}
Eif (keyword) {
return `<span class="text-purple-600 dark:text-purple-400">${keyword}</span>`;
}
return match;
}
);
} else if (language === "bash" || language === "shell") {
// Combined pattern for Bash
const bashPattern = /(#.*$)|("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')|(\b(?:if|then|else|elif|fi|for|do|done|while|case|esac|function|return|exit|cd|ls|mkdir|rm|cp|mv|echo|cat|grep|sed|awk)\b)/gm;
highlighted = highlighted.replace(
bashPattern,
(match: string, comment: string, str: string, keyword: string): string => {
Iif (comment) {
return `<span class="text-gray-500 dark:text-gray-400 italic">${comment}</span>`;
}
if (str) {
return `<span class="text-green-600 dark:text-green-400">${str}</span>`;
}
Eif (keyword) {
return `<span class="text-purple-600 dark:text-purple-400">${keyword}</span>`;
}
return match;
}
);
}
return highlighted;
};
/**
* CodeBlock component - displays code with optional syntax highlighting and copy functionality
*
* ## Business Perspective
*
* Provides clear, readable code examples to technical teams implementing
* security controls, improving comprehension and reducing implementation errors.
* Critical for effective knowledge transfer and documentation. 📝
*
* ## Technical Perspective
*
* This component provides theme-aware code display without external dependencies.
* It uses simple regex-based syntax highlighting for common languages (TypeScript,
* JavaScript, Python, Bash). Supports copy-to-clipboard functionality and optional
* line numbers.
*
* **Security Note**: The 'code' prop should only contain trusted content. This
* component uses dangerouslySetInnerHTML for syntax highlighting with HTML spans.
* While HTML characters are escaped (&, <, >) before processing, the code input
* should not come from untrusted user sources to prevent potential XSS risks.
*
* @component
*
* @example
* ```tsx
* <CodeBlock
* language="typescript"
* code={`const hello = "world";\nconsole.log(hello);`}
* showLineNumbers={true}
* copyable={true}
* />
* ```
*/
const CodeBlock: React.FC<CodeBlockProps> = ({
language,
code,
showLineNumbers = false,
copyable = true,
className = "",
testId = "code-block",
}) => {
const [copied, setCopied] = useState(false);
const handleCopy = async (): Promise<void> => {
try {
await navigator.clipboard.writeText(code);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (err) {
console.error("Failed to copy code:", err);
}
};
// Memoize highlighted code and lines to avoid re-computing on every render
const { highlightedCode, highlightedLines } = useMemo(() => {
const highlighted = highlightCode(code, language);
return {
highlightedCode: highlighted,
highlightedLines: highlighted.split("\n"),
};
}, [code, language]);
return (
<div
className={`relative rounded-md overflow-hidden ${className}`}
data-testid={testId}
>
{/* Header with language and copy button */}
<div className="flex items-center justify-between px-lg py-md bg-gray-100 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
<span className="text-xs font-medium text-gray-600 dark:text-gray-400">
{language || "code"}
</span>
{copyable && (
<button
onClick={handleCopy}
className="text-xs px-md py-xs rounded bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors"
data-testid={`${testId}-copy-button`}
aria-label="Copy code to clipboard"
>
<span aria-live="polite" aria-atomic="true">
{copied ? "Copied!" : "Copy"}
</span>
</button>
)}
</div>
{/* Code content */}
<div className="bg-gray-50 dark:bg-gray-900 overflow-x-auto">
<pre className="p-lg text-sm">
<code className="font-mono">
{showLineNumbers ? (
<table className="w-full border-collapse">
<tbody>
{highlightedLines.map((line, index) => (
<tr key={index}>
<td className="pr-lg text-right text-gray-400 dark:text-gray-600 select-none align-top">
{index + 1}
</td>
<td
className="text-gray-800 dark:text-gray-200"
dangerouslySetInnerHTML={{ __html: line || " " }}
/>
</tr>
))}
</tbody>
</table>
) : (
<div
className="text-gray-800 dark:text-gray-200"
dangerouslySetInnerHTML={{ __html: highlightedCode }}
/>
)}
</code>
</pre>
</div>
</div>
);
};
export default CodeBlock;
|