mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
perf(lint): one ast-grep scan feeds both the lint gate and the flaky-test ratchet (#33903)
lint:ast-grep and check-flaky-test-pattern-ratchet each parsed the whole tree. ast-grep-gates.mjs scans once; the lint gate re-runs ast-grep only on files with warning-or-error findings (ast-grep's own report and exit code), and the flaky ratchet counts test-* matches from the shared scan. Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
@@ -287,7 +287,7 @@ jobs:
|
||||
if: needs.code_paths.outputs.static_analysis == 'true'
|
||||
run: |
|
||||
node_modules/.bin/ast-grep test --config config/sgconfig.yml --skip-snapshot-tests
|
||||
node_modules/.bin/ast-grep scan --config config/sgconfig.yml --min-severity warning --format github src config tests mobile
|
||||
node config/scripts/ast-grep-gates.mjs --format=github
|
||||
|
||||
# Why the full tree: it takes ~2s, and the pre-commit hook is skippable.
|
||||
- name: Check formatting
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
// One ast-grep scan for both gates: the lint gate (warning and up) and the flaky-test ratchet (`test-*` rules).
|
||||
import { spawnSync } from 'node:child_process'
|
||||
import process from 'node:process'
|
||||
import { pathToFileURL } from 'node:url'
|
||||
import {
|
||||
AST_GREP_CONFIG,
|
||||
AST_GREP_SCAN_PATHS,
|
||||
resolveAstGrepBinary,
|
||||
scanAstGrep
|
||||
} from './ast-grep-scan.mjs'
|
||||
import { main as checkFlakyTestPatterns } from './check-flaky-test-pattern-ratchet.mjs'
|
||||
|
||||
const LINT_SEVERITIES = new Set(['error', 'warning'])
|
||||
// Windows caps a command line near 32K characters; past this, rescan the whole tree.
|
||||
const MAX_PATH_ARGUMENT_CHARS = 24_000
|
||||
|
||||
export function lintFindingFiles(matches) {
|
||||
return [
|
||||
...new Set(
|
||||
matches
|
||||
.filter((match) => LINT_SEVERITIES.has(match.severity))
|
||||
.map((match) => match.file.split('\\').join('/'))
|
||||
)
|
||||
].sort()
|
||||
}
|
||||
|
||||
// Rules match file by file, so rescanning only the reported files gives ast-grep's own report and exit status.
|
||||
function reportLintFindings(root, files, format) {
|
||||
const paths =
|
||||
files.reduce((total, file) => total + file.length + 1, 0) <= MAX_PATH_ARGUMENT_CHARS
|
||||
? files
|
||||
: AST_GREP_SCAN_PATHS
|
||||
const result = spawnSync(
|
||||
resolveAstGrepBinary(),
|
||||
[
|
||||
'scan',
|
||||
'--config',
|
||||
AST_GREP_CONFIG,
|
||||
'--min-severity',
|
||||
'warning',
|
||||
...(format ? ['--format', format] : []),
|
||||
'--',
|
||||
...paths
|
||||
],
|
||||
{ cwd: root, stdio: 'inherit', windowsHide: true }
|
||||
)
|
||||
if (result.error) {
|
||||
throw result.error
|
||||
}
|
||||
return result.status ?? 1
|
||||
}
|
||||
|
||||
export function main(root = process.cwd(), argv = []) {
|
||||
const format = argv
|
||||
.find((argument) => argument.startsWith('--format='))
|
||||
?.slice('--format='.length)
|
||||
const matches = scanAstGrep(root)
|
||||
const files = lintFindingFiles(matches)
|
||||
const lint = files.length > 0 ? reportLintFindings(root, files, format) : 0
|
||||
const flaky = checkFlakyTestPatterns(root, undefined, matches)
|
||||
return Math.max(lint, flaky)
|
||||
}
|
||||
|
||||
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
||||
process.exitCode = main(process.cwd(), process.argv.slice(2))
|
||||
}
|
||||
@@ -174,8 +174,9 @@ export function selectTypecheckProjects(files) {
|
||||
|
||||
function listRatchetChecks(scriptsDir) {
|
||||
const scripts = new Set(listRatchetScripts(scriptsDir))
|
||||
// `pnpm lint` runs it although CI gives it a dedicated step outside run-ratchets.mjs.
|
||||
// `pnpm lint` runs these although CI checks them in their own steps outside run-ratchets.mjs.
|
||||
scripts.add('check-main-sync-spawn-ratchet.mjs')
|
||||
scripts.add('check-flaky-test-pattern-ratchet.mjs')
|
||||
return [...scripts].sort().map((script) => ({
|
||||
name: script.replace(/^check-|\.mjs$/g, ''),
|
||||
args: [`config/scripts/${script}`],
|
||||
|
||||
@@ -9,6 +9,7 @@ import { listTrackedFiles, runCountRatchet } from './ratchet-baseline.mjs'
|
||||
|
||||
const BASELINE_PATH = 'config/flaky-test-pattern-baseline.txt'
|
||||
const RULE_FILTER = '^test-'
|
||||
const FLAKY_RULE = new RegExp(RULE_FILTER)
|
||||
const HEADER = [
|
||||
'# Flaky-test pattern findings per rule and file (config/ast-grep/rules/flaky-tests).',
|
||||
'# This is a RATCHET: counts may only go DOWN. Run `pnpm lint:ast-grep:flaky-tests` to list them.',
|
||||
@@ -28,10 +29,14 @@ export function countFindings(matches, isTracked = () => true) {
|
||||
return counts
|
||||
}
|
||||
|
||||
export function collectFlakyTestPatternCounts(root = process.cwd()) {
|
||||
/** `matches` may come from an unfiltered scan another gate shares; only `test-*` rules count. */
|
||||
export function collectFlakyTestPatternCounts(root = process.cwd(), matches = undefined) {
|
||||
// Tracked files only, so untracked scratch never changes the baseline.
|
||||
const tracked = new Set(listTrackedFiles(root, []))
|
||||
return countFindings(scanAstGrep(root, { filter: RULE_FILTER }), (file) => tracked.has(file))
|
||||
const flaky = (matches ?? scanAstGrep(root, { filter: RULE_FILTER })).filter((match) =>
|
||||
FLAKY_RULE.test(match.ruleId)
|
||||
)
|
||||
return countFindings(flaky, (file) => tracked.has(file))
|
||||
}
|
||||
|
||||
function git(root, args) {
|
||||
@@ -74,13 +79,13 @@ export function changedFileScope(changed) {
|
||||
return (key) => changed.has(key.slice(key.indexOf(':') + 1))
|
||||
}
|
||||
|
||||
export function main(root = process.cwd(), arg = undefined) {
|
||||
export function main(root = process.cwd(), arg = undefined, matches = undefined) {
|
||||
return runCountRatchet(
|
||||
{
|
||||
name: 'flaky-test-pattern',
|
||||
baselinePath: BASELINE_PATH,
|
||||
header: HEADER,
|
||||
collect: collectFlakyTestPatternCounts,
|
||||
collect: (dir) => collectFlakyTestPatternCounts(dir, matches),
|
||||
scope: (dir) => changedFileScope(listChangedFiles(dir)),
|
||||
unit: 'flaky-test finding(s)',
|
||||
fix: [
|
||||
|
||||
@@ -15,8 +15,7 @@ export const LINT_STEPS = [
|
||||
'check:test-file-runners',
|
||||
'verify:localization-catalogs',
|
||||
'verify:localization-coverage',
|
||||
'check:flaky-test-pattern-ratchet',
|
||||
'lint:ast-grep',
|
||||
'lint:ast-grep-gates',
|
||||
'check:main-sync-spawn-ratchet',
|
||||
'lint:js-plugins',
|
||||
'oxlint',
|
||||
@@ -50,7 +49,9 @@ export const LINT_STEPS = [
|
||||
const BARE_COMMANDS = {
|
||||
oxlint: 'oxlint',
|
||||
// process-host and compat-boundary imports from one TypeScript parse, as CI runs them.
|
||||
'check:import-boundaries': 'bun config/scripts/check-import-boundaries.mjs'
|
||||
'check:import-boundaries': 'bun config/scripts/check-import-boundaries.mjs',
|
||||
// lint:ast-grep and the flaky-test ratchet from one scan, as CI runs them.
|
||||
'lint:ast-grep-gates': 'node config/scripts/ast-grep-gates.mjs'
|
||||
}
|
||||
|
||||
function parseMaxWorkers(argv) {
|
||||
|
||||
@@ -11,7 +11,9 @@ const RUN_ELSEWHERE = new Set([
|
||||
// Needs the PR diff base; the cross-version-wire job runs it.
|
||||
'check-runtime-launcher-protocol-ratchet.mjs',
|
||||
// Its own background preflight step, so it overlaps lint and typecheck.
|
||||
'check-main-sync-spawn-ratchet.mjs'
|
||||
'check-main-sync-spawn-ratchet.mjs',
|
||||
// ast-grep-gates.mjs checks it from the ast-grep lint scan instead of scanning again.
|
||||
'check-flaky-test-pattern-ratchet.mjs'
|
||||
])
|
||||
|
||||
export function listRatchetScripts(scriptsDir) {
|
||||
|
||||
Reference in New Issue
Block a user