mirror of
https://github.com/stablyai/orca.git
synced 2026-09-21 16:02:20 +00:00
perf: avoid rescanning emitted source in analysis guards (#18920)
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { execFileSync } from 'node:child_process'
|
||||
import { stripTypeScriptTypes } from 'node:module'
|
||||
import { performance } from 'node:perf_hooks'
|
||||
import { blankStringContents as after } from '../../src/shared/source-scan/source-tree-scan.ts'
|
||||
|
||||
const ref = process.argv[2]
|
||||
if (!ref) {
|
||||
throw new Error('Usage: node config/scripts/source-string-blanking-benchmark.mjs <baseline-ref>')
|
||||
}
|
||||
const source = execFileSync('git', ['show', `${ref}:src/shared/source-scan/source-tree-scan.ts`], {
|
||||
encoding: 'utf8'
|
||||
})
|
||||
const { blankStringContents: before } = await import(
|
||||
`data:text/javascript;base64,${Buffer.from(stripTypeScriptTypes(source)).toString('base64')}`
|
||||
)
|
||||
const tokens = [
|
||||
'a',
|
||||
'/',
|
||||
'*',
|
||||
' ',
|
||||
'\n',
|
||||
'\r',
|
||||
'\t',
|
||||
'\u00a0',
|
||||
'\u2028',
|
||||
'"',
|
||||
"'",
|
||||
'`',
|
||||
'${',
|
||||
'}',
|
||||
'{',
|
||||
'\\',
|
||||
'(',
|
||||
')',
|
||||
'[',
|
||||
']',
|
||||
'=',
|
||||
'+',
|
||||
'-',
|
||||
';'
|
||||
]
|
||||
let seed = 173
|
||||
for (let sample = 0; sample < 3000; sample++) {
|
||||
let input = ''
|
||||
for (let token = 0; token < 40; token++) {
|
||||
seed = (Math.imul(seed, 1664525) + 1013904223) >>> 0
|
||||
input += tokens[seed % tokens.length]
|
||||
}
|
||||
assert.equal(after(input), before(input), JSON.stringify(input))
|
||||
assert.equal(after(input, true), before(input, true), JSON.stringify(input))
|
||||
}
|
||||
function measure(fn, input) {
|
||||
const samples = []
|
||||
for (let run = 0; run < 3; run++) {
|
||||
const start = performance.now()
|
||||
fn(input)
|
||||
samples.push(performance.now() - start)
|
||||
}
|
||||
return samples.sort((a, b) => a - b)[1]
|
||||
}
|
||||
const results = []
|
||||
for (const lines of [100, 1000, 5000, 10000]) {
|
||||
const input = 'const x = value / 2;\n'.repeat(lines)
|
||||
assert.equal(after(input), before(input))
|
||||
results.push({
|
||||
lines,
|
||||
bytes: Buffer.byteLength(input),
|
||||
beforeMs: measure(before, input),
|
||||
afterMs: measure(after, input)
|
||||
})
|
||||
}
|
||||
console.log(
|
||||
JSON.stringify(
|
||||
{ node: process.version, platform: process.platform, differentialCases: 3000, results },
|
||||
null,
|
||||
2
|
||||
)
|
||||
)
|
||||
@@ -47,6 +47,13 @@ describe('stripComments', () => {
|
||||
})
|
||||
|
||||
describe('blankStringContents', () => {
|
||||
it('does not rescan the accumulated source for each division operator', () => {
|
||||
const source = 'const x = value / 2;\n'.repeat(10000)
|
||||
const started = performance.now()
|
||||
expect(blankStringContents(source)).toBe(source)
|
||||
expect(performance.now() - started).toBeLessThan(200)
|
||||
})
|
||||
|
||||
it('neutralises parentheses inside a string so a call is matched whole', () => {
|
||||
// A shell script embedded as a string closed the call early, so the options
|
||||
// object fell outside the match and its flags read as absent.
|
||||
|
||||
@@ -179,8 +179,7 @@ export function blankStringContentsDesynced(source: string): boolean {
|
||||
* each also has a prefix reading: `!` (non-null assertion vs `!/re/.test(x)`),
|
||||
* `+` `-` `*` `%` `^` `~` (postfix `--`/`++`), and `>` `}` (JSX close).
|
||||
*/
|
||||
function startsRegexLiteral(emitted: string): boolean {
|
||||
const prev = emitted.replace(/\s+$/, '').at(-1)
|
||||
function startsRegexLiteral(prev: string | undefined): boolean {
|
||||
return prev === undefined || '(,=:[&|?;'.includes(prev)
|
||||
}
|
||||
|
||||
@@ -209,6 +208,7 @@ function findRegexLiteralEnd(source: string, start: number): number {
|
||||
|
||||
export function blankStringContents(source: string, reportDesync = false): string {
|
||||
let out = ''
|
||||
let lastSignificantChar: string | undefined
|
||||
let index = 0
|
||||
let quote: string | null = null
|
||||
// Brace depth per interpolation, so a `}` inside `${ { a: 1 } }` does not
|
||||
@@ -220,6 +220,7 @@ export function blankStringContents(source: string, reportDesync = false): strin
|
||||
templates.push(0)
|
||||
quote = null
|
||||
out += '${'
|
||||
lastSignificantChar = '{'
|
||||
index += 2
|
||||
continue
|
||||
}
|
||||
@@ -232,6 +233,7 @@ export function blankStringContents(source: string, reportDesync = false): strin
|
||||
templates.pop()
|
||||
quote = '`'
|
||||
out += char
|
||||
lastSignificantChar = char
|
||||
index += 1
|
||||
continue
|
||||
}
|
||||
@@ -256,6 +258,7 @@ export function blankStringContents(source: string, reportDesync = false): strin
|
||||
if (char === quote) {
|
||||
quote = null
|
||||
out += char
|
||||
lastSignificantChar = char
|
||||
} else {
|
||||
out += char === '\n' ? char : ' '
|
||||
}
|
||||
@@ -270,10 +273,11 @@ export function blankStringContents(source: string, reportDesync = false): strin
|
||||
// comments first, but this runs standalone too, and at index 0 a file
|
||||
// starting with a banner comment read as one giant regex.
|
||||
const next = source[index + 1]
|
||||
if (char === '/' && next !== '/' && next !== '*' && startsRegexLiteral(out)) {
|
||||
if (char === '/' && next !== '/' && next !== '*' && startsRegexLiteral(lastSignificantChar)) {
|
||||
const end = findRegexLiteralEnd(source, index)
|
||||
if (end !== -1) {
|
||||
out += `/${' '.repeat(end - index - 1)}`
|
||||
lastSignificantChar = '/'
|
||||
index = end
|
||||
continue
|
||||
}
|
||||
@@ -282,6 +286,9 @@ export function blankStringContents(source: string, reportDesync = false): strin
|
||||
quote = char
|
||||
}
|
||||
out += char
|
||||
if (/\S/.test(char)) {
|
||||
lastSignificantChar = char
|
||||
}
|
||||
index += 1
|
||||
}
|
||||
if (reportDesync) {
|
||||
|
||||
Reference in New Issue
Block a user