fix(webapp): avoid mutating render inputs (#4716)
## Summary Keeps render inputs and shared regular expressions immutable. Grouped selects now compute each section's shortcut offset directly from preceding sections, which also makes numeric shortcuts follow the displayed item order reliably.
This commit is contained in:
@@ -221,35 +221,35 @@ export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(
|
||||
const [modalCopied, setModalCopied] = useState(false);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [isWrapped, setIsWrapped] = useState(wrap);
|
||||
const normalizedCode = code?.trim() ?? "";
|
||||
|
||||
const onCopied = useCallback(
|
||||
(event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
navigator.clipboard.writeText(code);
|
||||
navigator.clipboard.writeText(normalizedCode);
|
||||
setCopied(true);
|
||||
setTimeout(() => {
|
||||
setCopied(false);
|
||||
}, 1500);
|
||||
},
|
||||
[code]
|
||||
[normalizedCode]
|
||||
);
|
||||
|
||||
const onModalCopied = useCallback(
|
||||
(event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
navigator.clipboard.writeText(code);
|
||||
navigator.clipboard.writeText(normalizedCode);
|
||||
setModalCopied(true);
|
||||
setTimeout(() => {
|
||||
setModalCopied(false);
|
||||
}, 1500);
|
||||
},
|
||||
[code]
|
||||
[normalizedCode]
|
||||
);
|
||||
|
||||
code = code?.trim() ?? "";
|
||||
const lineCount = code.split("\n").length;
|
||||
const lineCount = normalizedCode.split("\n").length;
|
||||
const maxLineWidth = lineCount.toString().length;
|
||||
let maxHeight: string | undefined = undefined;
|
||||
if (maxLines && lineCount > maxLines) {
|
||||
@@ -345,7 +345,7 @@ export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(
|
||||
{shouldHighlight ? (
|
||||
<HighlightCode
|
||||
theme={theme}
|
||||
code={code}
|
||||
code={normalizedCode}
|
||||
language={language}
|
||||
showLineNumbers={showLineNumbers}
|
||||
highlightLines={highlightLines}
|
||||
@@ -373,7 +373,7 @@ export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(
|
||||
)}
|
||||
dir="ltr"
|
||||
>
|
||||
{highlightSearchText(code, searchTerm)}
|
||||
{highlightSearchText(normalizedCode, searchTerm)}
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
@@ -400,7 +400,7 @@ export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(
|
||||
{shouldHighlight ? (
|
||||
<HighlightCode
|
||||
theme={theme}
|
||||
code={code}
|
||||
code={normalizedCode}
|
||||
language={language}
|
||||
showLineNumbers={showLineNumbers}
|
||||
highlightLines={highlightLines}
|
||||
@@ -415,7 +415,7 @@ export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(
|
||||
className="overflow-auto px-3 py-3 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-surface-control"
|
||||
>
|
||||
<pre className="relative mr-2 p-2 font-mono text-base leading-relaxed" dir="ltr">
|
||||
{highlightSearchText(code, searchTerm)}
|
||||
{highlightSearchText(normalizedCode, searchTerm)}
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -187,7 +187,7 @@ export function ReportFindingLine({
|
||||
* entities mono, verdict phrases bright and medium, everything else dimmed.
|
||||
* Colour stays reserved for severity, so emphasis here is weight only.
|
||||
*/
|
||||
const QUANTITY_RE = /~?\d[\d,.]*\s?(?:%|×|\/min|ms\b|s\b|min\b|h\b)?/g;
|
||||
const QUANTITY_RE = /~?\d[\d,.]*\s?(?:%|×|\/min|ms\b|s\b|min\b|h\b)?/;
|
||||
|
||||
const VERDICT_PHRASES = [
|
||||
"not your code",
|
||||
@@ -249,7 +249,6 @@ export function ReportProse({ text, entities }: { text: string; entities?: strin
|
||||
segments = splitBy(
|
||||
segments,
|
||||
(t) => {
|
||||
QUANTITY_RE.lastIndex = 0;
|
||||
const m = QUANTITY_RE.exec(t);
|
||||
return m && m[0].trim().length > 0 ? { start: m.index, end: m.index + m[0].length } : null;
|
||||
},
|
||||
|
||||
@@ -413,19 +413,20 @@ function SelectGroupedRenderer<TItem>({
|
||||
) => React.ReactNode;
|
||||
enableItemShortcuts: boolean;
|
||||
}) {
|
||||
let count = 0;
|
||||
return (
|
||||
<>
|
||||
{items.map((section, index) => {
|
||||
const previousItem = items.at(index - 1);
|
||||
count += previousItem ? previousItem.items.length : 0;
|
||||
const startIndex = items
|
||||
.slice(0, index)
|
||||
.reduce((count, previousSection) => count + previousSection.items.length, 0);
|
||||
|
||||
return (
|
||||
<Fragment key={index}>
|
||||
{children(section.items as ItemFromSection<TItem>[], {
|
||||
shortcutsEnabled: enableItemShortcuts,
|
||||
section: {
|
||||
title: section.title,
|
||||
startIndex: count - 1,
|
||||
startIndex,
|
||||
count: section.items.length,
|
||||
},
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user