Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/pr-regression-gates.yml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,16 @@ jobs:
npm run sanitizing:line_length -- --base-ref "${GATE_BASE_REF:-origin/main}"
npm run sanitizing:file_size -- --base-ref "${GATE_BASE_REF:-origin/main}"

- name: "Check sanitizing layout and boundary regressions (#3168)"
timeout-minutes: 10
env:
GATE_BASE_REF: ${{ github.event.pull_request.base.ref && format('origin/{0}', github.event.pull_request.base.ref) || '' }}
run: |
npm run sanitizing:file_layout -- --base-ref "${GATE_BASE_REF:-origin/main}"
npm run sanitizing:tauri_edge -- --base-ref "${GATE_BASE_REF:-origin/main}"
npm run sanitizing:global_state -- --base-ref "${GATE_BASE_REF:-origin/main}"
npm run sanitizing:process_env -- --base-ref "${GATE_BASE_REF:-origin/main}"

rust-regression:
name: rust-regression
# Skip branch-deletion push events while still requiring pull request validation.
Expand Down
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@
"dup:changed:self": "node scripts/check-test-duplication.mjs --self-test",
"sanitizing:line_length": "node scripts/sanitizing_line_length.mjs",
"sanitizing:file_size": "node scripts/sanitizing_file_size.mjs",
"sanitizing:file_layout": "node scripts/sanitizing_file_layout.mjs",
"sanitizing:tauri_edge": "node scripts/sanitizing_tauri_edge.mjs",
"sanitizing:global_state": "node scripts/sanitizing_global_state.mjs",
"sanitizing:process_env": "node scripts/sanitizing_process_env.mjs",
"sanitizing:self": "node --test scripts/sanitizing_tests.mjs"
},
"dependencies": {
Expand Down
103 changes: 103 additions & 0 deletions scripts/sanitizing_file_layout.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { codeOrder, gateCli, isMain } from './sanitizing_baseline.mjs';
import { scanRust } from './sanitizing_rust_scan.mjs';

const relevantPath = new RegExp("(?:^|::)(?:test)$");

Check warning on line 4 in scripts/sanitizing_file_layout.mjs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use a regular expression literal instead of the 'RegExp' constructor.

See more on https://sonarcloud.io/project/issues?id=mblua_AgentsCommander&issues=AaFkOSb1JToUXmEtQemU&open=AaFkOSb1JToUXmEtQemU&pullRequest=3173
function resolveAlias(scan, input, scope) {
const resolved = scan.resolvePath(input,scope);
if (!resolved.ambiguous) return resolved;
const raw = typeof input === 'string' ? input : input.map(t => t.name ?? t.text).join('');
const suffix = raw.split('::').slice(1).join('::');
const candidates = [resolved.path,raw];
for (let b = resolved.binding; b; b = b.previous) if (b.target)
candidates.push(b.target + (suffix ? '::' + suffix : ''));
return { ...resolved, path: candidates.find(p => p && relevantPath.test(p)) ?? resolved.path };
}

// Finite syntactic signals, using the shared lexer, scope and production model.
function metric() { return { counts: {}, sites: {}, commands: new Map(), warnings: [] }; }
function hit(m, rule, line, identity) {
m.counts[rule] = (m.counts[rule] ?? 0) + 1;
(m.sites[rule] ??= []).push({ line, ...(identity ? { identity } : {}) });

Check warning on line 20 in scripts/sanitizing_file_layout.mjs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Extract the assignment of "m.sites[rule]" from this expression.

See more on https://sonarcloud.io/project/issues?id=mblua_AgentsCommander&issues=AaFkOSb1JToUXmEtQemV&open=AaFkOSb1JToUXmEtQemV&pullRequest=3173
}
function warning(m, path, line, resolved) {
if (resolved.ambiguous) m.warnings.push({ path, line, reason: resolved.reason ?? 'ambiguous relevant alias',
remedy: 'use an explicit unambiguous import; signal counted conservatively' });
}
export function runGate(snapshot) {

Check failure on line 26 in scripts/sanitizing_file_layout.mjs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 28 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=mblua_AgentsCommander&issues=AaFkOSb1JToUXmEtQemW&open=AaFkOSb1JToUXmEtQemW&pullRequest=3173
const diagnostics = [], measurements = { base: [], head: [] };
try {
const trees = [snapshot.baseTree, snapshot.headTree], totals = [{}, {}], byPath = [new Map(), new Map()];
for (const [side, tree] of trees.entries()) {
for (const path of [...tree.keys()].filter(inUniverse).sort(codeOrder)) {
const m = measure(path, scanRust(path, snapshot.readBlob(tree.get(path))));
byPath[side].set(path, m);
for (const rule of rules) totals[side][rule] = (totals[side][rule] ?? 0) + (m.counts[rule] ?? 0);
measurements[side ? 'head' : 'base'].push({ path, counts: m.counts, sites: m.sites });
if (side) diagnostics.push(...m.warnings.map(d => ({ gate, status: 'WARN', ...d })));
}
}
const violation = (rule, path, line, base, head, reason) => diagnostics.push({ gate, status: 'FAIL', rule, path, line,
base, head, baseOid: snapshot.baseOid, headOid: snapshot.headOid, reason,
remedy: 'remove the new signal or use the source-defined owner; do not raise the baseline' });
for (const rule of rules) if ((totals[1][rule] ?? 0) > (totals[0][rule] ?? 0)) {
const source = [...byPath[1]].find(([p,m]) => (m.counts[rule] ?? 0) > (byPath[0].get(p)?.counts[rule] ?? 0));
violation(rule, source?.[0] ?? '<universe>', source?.[1].sites[rule]?.[0]?.line ?? 1,
totals[0][rule] ?? 0, totals[1][rule], 'global subrule debt increased');
}
for (const [path, m] of byPath[1]) {
if (!snapshot.baseTree.has(path)) for (const rule of newFileRules) if (m.counts[rule])
violation(rule, path, m.sites[rule][0].line, 0, m.counts[rule], 'new file must satisfy this subrule even with compensated totals');
extraChecks(path, m, byPath[0].get(path), violation);
}
snapshot.assertStableHead();
const fail = diagnostics.some(d => d.status === 'FAIL');
return { gate, status: fail ? 'FAIL' : 'PASS', exit: fail ? 1 : 0, requestedRef: snapshot.requestedRef,
refOid: snapshot.refOid, baseOid: snapshot.baseOid, headOid: snapshot.headOid, totals, measurements, diagnostics };
} catch (e) {
return { gate, status: 'ERROR', exit: e.exit ?? 3, baseOid: snapshot.baseOid, headOid: snapshot.headOid,
diagnostics: [...diagnostics, { gate, reason: e.message, remedy: 'repair inputs and rerun; no verdict available' }] };
}
}

const gate = '05';
const rules = ['mod_rs', 'test_files', 'inline_mod', 'path', 'glob', 'pub_mod', 'pub_super_mod', 'pub_in_mod'];
const newFileRules = rules.slice(0, 5);
const inUniverse = p => /^src-tauri\/(?:src|tests)\/.*\.rs$/.test(p);
const extraChecks = () => {};
function measure(path, scan) {

Check failure on line 67 in scripts/sanitizing_file_layout.mjs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 44 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=mblua_AgentsCommander&issues=AaFkOSb1JToUXmEtQemX&open=AaFkOSb1JToUXmEtQemX&pullRequest=3173
const m = metric(), integration = path.startsWith('src-tauri/tests/') || /(?:^|\/)tests\//.test(path);
if (path.endsWith('/mod.rs')) hit(m, 'mod_rs', 1);
let testLine;
for (const a of scan.attributes) for (const meta of a.signals) {
const r = resolveAlias(scan,meta.name,a.scope);
if (['test', 'tokio::test', 'async_std::test'].includes(r.path) || r.ambiguous && /(?:^|::)test$/.test(r.path ?? meta.name)) {
testLine ??= a.line; warning(m, path, a.line, r);
}
if (meta.name === 'path') hit(m, 'path', a.line);
}
if (testLine && !path.endsWith('/tests.rs') && !integration) hit(m, 'test_files', testLine);
const ts = scan.tokens;
for (const item of scan.items) {
if (item.kind === 'mod') {
if (item.bodyScope) hit(m, 'inline_mod', item.line);
// Visibility belongs to this item, including attributes preceding pub(...).
const prefix = ts.filter(t => t.start >= item.start && t.index < item.first);
const pub = prefix.findIndex(t => t.name === 'pub');
if (pub >= 0) {
if (prefix[pub + 1]?.text !== '(') {
if (path !== 'src-tauri/src/lib.rs') hit(m, 'pub_mod', item.line);
} else if (prefix[pub + 2]?.name === 'super') hit(m, 'pub_super_mod', item.line);
else if (prefix[pub + 2]?.name === 'in') hit(m, 'pub_in_mod', item.line);
}
}
}
for (const scope of scan.scopes) for (const entry of scope.imports) if (entry.glob) {
if (path.endsWith('/tests.rs') && entry.target === 'super::*') continue;
const use = scan.items.find(i => i.kind === 'use' && i.scope === scope &&
ts.slice(i.first, i.last).some(t => t.text === '*'));
hit(m, 'glob', use?.line ?? 1);
}
return m;
}

if (isMain(import.meta.url)) process.exitCode = await gateCli(runGate);
153 changes: 153 additions & 0 deletions scripts/sanitizing_global_state.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import { codeOrder, gateCli, isMain } from './sanitizing_baseline.mjs';
import { scanRust } from './sanitizing_rust_scan.mjs';

const relevantPath = /(?:^|::)(?:Mutex|RwLock|Atomic\w*|OnceLock|OnceCell|Lazy|LazyLock|Cell|RefCell)$/;
function resolveAlias(scan, input, scope) {
const resolved = scan.resolvePath(input,scope);
if (!resolved.ambiguous) return resolved;
const raw = typeof input === 'string' ? input : input.map(t => t.name ?? t.text).join('');
const suffix = raw.split('::').slice(1).join('::');
const candidates = [resolved.path,raw];
for (let b = resolved.binding; b; b = b.previous) if (b.target)
candidates.push(b.target + (suffix ? '::' + suffix : ''));
return { ...resolved, path: candidates.find(p => p && relevantPath.test(p)) ?? resolved.path };
}

// Finite syntactic signals, using the shared lexer, scope and production model.
function qualified(scan, tokens, i) {
let end = i + 1;
while (tokens[end]?.text === '::' && tokens[end + 1]?.kind === 'ident') end += 2;
const start = tokens[i - 1]?.text === '::' && tokens[i - 2]?.kind !== 'ident' ? i - 1 : i;
const resolved = resolveAlias(scan,tokens.slice(start, end),tokens[i].scope);
return { ...resolved, end, raw: tokens.slice(start, end).map(t => t.name ?? t.text).join('') };
}
function metric() { return { counts: {}, sites: {}, commands: new Map(), warnings: [] }; }
function hit(m, rule, line, identity) {
m.counts[rule] = (m.counts[rule] ?? 0) + 1;
(m.sites[rule] ??= []).push({ line, ...(identity ? { identity } : {}) });

Check warning on line 27 in scripts/sanitizing_global_state.mjs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Extract the assignment of "m.sites[rule]" from this expression.

See more on https://sonarcloud.io/project/issues?id=mblua_AgentsCommander&issues=AaFkOSb4JToUXmEtQeme&open=AaFkOSb4JToUXmEtQeme&pullRequest=3173
}
function warning(m, path, line, resolved) {
if (resolved.ambiguous) m.warnings.push({ path, line, reason: resolved.reason ?? 'ambiguous relevant alias',
remedy: 'use an explicit unambiguous import; signal counted conservatively' });
}
export function runGate(snapshot) {

Check failure on line 33 in scripts/sanitizing_global_state.mjs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 28 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=mblua_AgentsCommander&issues=AaFkOSb4JToUXmEtQemf&open=AaFkOSb4JToUXmEtQemf&pullRequest=3173
const diagnostics = [], measurements = { base: [], head: [] };
try {
const trees = [snapshot.baseTree, snapshot.headTree], totals = [{}, {}], byPath = [new Map(), new Map()];
for (const [side, tree] of trees.entries()) {
for (const path of [...tree.keys()].filter(inUniverse).sort(codeOrder)) {
const m = measure(path, scanRust(path, snapshot.readBlob(tree.get(path))));
byPath[side].set(path, m);
for (const rule of rules) totals[side][rule] = (totals[side][rule] ?? 0) + (m.counts[rule] ?? 0);
measurements[side ? 'head' : 'base'].push({ path, counts: m.counts, sites: m.sites });
if (side) diagnostics.push(...m.warnings.map(d => ({ gate, status: 'WARN', ...d })));
}
}
const violation = (rule, path, line, base, head, reason) => diagnostics.push({ gate, status: 'FAIL', rule, path, line,
base, head, baseOid: snapshot.baseOid, headOid: snapshot.headOid, reason,
remedy: 'remove the new signal or use the source-defined owner; do not raise the baseline' });
for (const rule of rules) if ((totals[1][rule] ?? 0) > (totals[0][rule] ?? 0)) {
const source = [...byPath[1]].find(([p,m]) => (m.counts[rule] ?? 0) > (byPath[0].get(p)?.counts[rule] ?? 0));
violation(rule, source?.[0] ?? '<universe>', source?.[1].sites[rule]?.[0]?.line ?? 1,
totals[0][rule] ?? 0, totals[1][rule], 'global subrule debt increased');
}
for (const [path, m] of byPath[1]) {
if (!snapshot.baseTree.has(path)) for (const rule of newFileRules) if (m.counts[rule])
violation(rule, path, m.sites[rule][0].line, 0, m.counts[rule], 'new file must satisfy this subrule even with compensated totals');
extraChecks(path, m, byPath[0].get(path), violation);
}
snapshot.assertStableHead();
const fail = diagnostics.some(d => d.status === 'FAIL');
return { gate, status: fail ? 'FAIL' : 'PASS', exit: fail ? 1 : 0, requestedRef: snapshot.requestedRef,
refOid: snapshot.refOid, baseOid: snapshot.baseOid, headOid: snapshot.headOid, totals, measurements, diagnostics };
} catch (e) {
return { gate, status: 'ERROR', exit: e.exit ?? 3, baseOid: snapshot.baseOid, headOid: snapshot.headOid,
diagnostics: [...diagnostics, { gate, reason: e.message, remedy: 'repair inputs and rerun; no verdict available' }] };
}
}

const gate = '08';
const rules = ['globals'];
const newFileRules = rules;
const inUniverse = p => /^src-tauri\/src\/.*\.rs$/.test(p);
const extraChecks = () => {};
const stateType = /(?:^|::)(?:Mutex|RwLock|Atomic\w*|OnceLock|OnceCell|Lazy|LazyLock)$/;
function canonical(scan, tokens) {
const out = [];
for (let i = 0; i < tokens.length; i++) {
if (tokens[i].text === '::' && tokens[i + 1]?.kind === 'ident' && tokens[i - 1]?.kind !== 'ident') i++;
const t = tokens[i];
if (t.kind === 'string') { out.push('@literal'); continue; }
if (t.kind === 'ident' && tokens[i - 1]?.text !== '.' && !(tokens[i - 1]?.text === '::' && tokens[i - 2]?.kind === 'ident')) {
const r = qualified(scan,tokens,i);
if (r.ambiguous || r.shadowed || !r.path) return null;
out.push(r.path); i = r.end - 1;

Check warning on line 84 in scripts/sanitizing_global_state.mjs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this assignment of "i".

See more on https://sonarcloud.io/project/issues?id=mblua_AgentsCommander&issues=AaFkOSb4JToUXmEtQemg&open=AaFkOSb4JToUXmEtQemg&pullRequest=3173
} else out.push(t.name ?? t.text);
}
return out.join(' ');
}
function literalClosure(scan, tokens) {
if (tokens[0]?.text !== '||') return false;
let body = tokens.slice(1);
while (body[0]?.text === '{' && body[0].mate === body.at(-1)?.index) body = body.slice(1,-1);
const value = canonical(scan,body);
return value === 'regex::Regex::new ( @literal ) . unwrap ( )' ||
value === 'regex::Regex::new ( @literal ) . expect ( @literal )';
}
function readonlyRegex(scan, item, type, init) {
const typeText = canonical(scan,type);
if (typeText === 'std::sync::LazyLock < regex::Regex >') {
const open = init.findIndex(t => t.text === '(');
if (open < 0 || init[open].mate !== init.at(-1)?.index) return false;
return canonical(scan,init.slice(0,open)) === 'std::sync::LazyLock::new' && literalClosure(scan,init.slice(open + 1,-1));
}
if (typeText !== 'std::sync::OnceLock < regex::Regex >' || !item ||
canonical(scan,init) !== 'std::sync::OnceLock::new ( )') return false;
const ts = scan.tokens;
if (ts.filter(t => t.start >= item.start && t.index < item.first).some(t => t.name === 'pub')) return false;
const fn = scan.items.filter(i => i.kind === 'fn' && i.bodyStart < item.start && i.end > item.end).at(-1);

Check warning on line 108 in scripts/sanitizing_global_state.mjs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `.findLast(…)` over `.filter(…).at(-1)`.

See more on https://sonarcloud.io/project/issues?id=mblua_AgentsCommander&issues=AaFkOSb4JToUXmEtQemh&open=AaFkOSb4JToUXmEtQemh&pullRequest=3173
if (!fn) return false;
const refs = ts.filter(t => t.start > fn.bodyStart && t.start < fn.end && t.name === item.name &&
!(t.index >= item.first && t.index <= item.last));
if (!refs.length) return false;
for (const ref of refs) {
const i = ref.index;
if ((ts[i - 1]?.text === '.' || ts[i - 1]?.text === '::' && ts[i - 2]?.kind === 'ident') || ts[i + 1]?.text !== '.' || ts[i + 2]?.name !== 'get_or_init' || ts[i + 3]?.text !== '(') return false;
const end = ts[i + 3].mate;
if (!literalClosure(scan,ts.slice(i + 4,end))) return false;
}
return true;
}
function measure(path, scan) {

Check failure on line 121 in scripts/sanitizing_global_state.mjs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 46 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=mblua_AgentsCommander&issues=AaFkOSb4JToUXmEtQemi&open=AaFkOSb4JToUXmEtQemi&pullRequest=3173
const m = metric(), ts = scan.tokens;
// The scanner deliberately does not expand macros. Inspect only the explicit
// static declarations in thread_local!'s token tree, never arbitrary macros.
const threadGroups = scan.groups.filter(g => ts[g.start - 1]?.text === '!' && ts[g.start - 2]?.name === 'thread_local');
const declarations = scan.items.filter(i => i.kind === 'static').map(item => ({ item, first: item.first, last: item.last, thread: false }));
for (const group of threadGroups) for (let i = group.start + 1; i < group.end; i++) if (ts[i].name === 'static') {
let end = i + 1;
for (; end < group.end && ts[end].text !== ';'; end++) if (['(','[','{'].includes(ts[end].text)) end = ts[end].mate;
if (end === group.end) throw new Error(`${path}:${ts[i].line}: thread_local static not delimitable`);
declarations.push({ item: null, first: i, last: end, thread: true }); i = end;
}
for (const d of declarations) {
if (scan.isTestOffset(ts[d.first].start)) continue;
const segment = ts.slice(d.first,d.last), colon = segment.findIndex(t => t.text === ':'), eq = segment.findIndex(t => t.text === '=');
if (colon < 0 || eq < colon) throw new Error(`${path}:${ts[d.first].line}: static type/initializer not delimitable`);
const type = segment.slice(colon + 1,eq), init = segment.slice(eq + 1);
let state = segment[1]?.name === 'mut';
for (let i = 0; i < type.length; i++) if (type[i].kind === 'ident' && !(type[i - 1]?.text === '::' && type[i - 2]?.kind === 'ident')) {
const r = qualified(scan,type,i), value = r.path ?? (r.ambiguous ? r.raw : '');
if (stateType.test(value) || d.thread && /(?:^|::)(?:Cell|RefCell)$/.test(value)) {
state = true; warning(m,path,type[i].line,r);
}
i = r.end - 1;
}
if (!state) continue;
if (segment[1]?.name !== 'mut' && readonlyRegex(scan,d.item,type,init)) continue;
hit(m,'globals',ts[d.first].line);
}
return m;
}

if (isMain(import.meta.url)) process.exitCode = await gateCli(runGate);
Loading
Loading