Repository navigation
Code Analysis Parameter Defaults
The lexical call producer could point a valid JavaScript default-parameter call at a function-body declaration that is outside the initializer's scope. Draft #1225 now withdraws that false target and keeps the call explicitly unresolved. The fix is propagated through the existing question/default-import/paired-investigation stack; no PR is merged.
import { f } from './target.mjs';
function entry(value = f()) {
function f() { return 'body'; }
return value;
}
console.log(entry());With target.mjs exporting function f() { return 'import'; }, Node 22 prints import. The original call record instead reports entry.mjs:2:24–27 as same-file-function, targeting the body declaration at entry.mjs:3:3. Removing that body declaration gives a control that both Node and the analyzer correctly associate with the import.
ECMAScript creates a separate environment for body declarations when parameter expressions exist; closures created in the parameter list cannot see those body declarations. The authored execution above independently checks the exact input. FunctionDeclarationInstantiation
TypeScript 5.9.3 supplies the misleading body symbol and emits semantic diagnostic 2373 when checking the JavaScript. That diagnostic does not make this JavaScript invalid. The typed TypeScript variant is rejected by semantic checking; it is not presented here as valid TypeScript. Archify's syntax-only capture must avoid treating that symbol as a reliable lexical target.
The trigger was found in an authored scope audit. A narrow scan of 617 tracked Archify JS/TS files and 874 parameter initializers found no matching direct-body-function collision. There is no observed production incident or prevalence claim.
The nine-line guard checks enclosing parameter nodes. If the alleged function declaration lies inside that parameter's function body, the call retains the existing unresolved/no-local-function-binding representation. It does not search by name for an outer replacement. Imported/outer calls, ordinary body calls and declarations within initializer-created function scopes keep their supported bindings. A body-local const probe already resolved correctly, so that working path was not changed.
Two behavioral regressions fail on the exact previous producer and pass with the guard. Independent review found no actionable issue; ten additional probes cover array/computed/destructured parameters, methods, nested defaults, closures, recursion and preserved initializer-created scopes. All 248 producer-layer module tests and 18 focused tests pass on Node 22.23.2/Python 3.13.16. The initial research used Node 22.23.3; primary validation and the public replay use 22.23.2.
| Fixed scope | Files | Calls | Candidate result |
|---|---|---|---|
| Authored minimal case and control | 3 | 6 | Exactly one false target withdrawn; direct-import control preserved |
Archify Labs bf0a29e7664f52973d06a8e6a608df2db1ecbd39
|
607 | 112,145 | Every call and other fact preserved |
EventEmitter3 a650549176ded0d0dcc52e27b03ae5669d7dcd7c
|
16 | 1,085 | Every call and other fact preserved |
Redux src c9e06506f88926e252daf5275495eba0c04bf8e3
|
17 | 87 | Every call and other fact preserved |
dependency-cruiser e6517db02e169f3db5c838bb1565ebd0fefeb452
|
1,157 | 12,876 | Every call and other fact preserved; ten existing fixture parse errors remain |
These are preservation controls, not a general accuracy measure. Source and unpacked archive raw facts match byte for byte in all five scopes. A fresh public fetch passed the same checks and both authored runtime executions; the four target repositories are complete tracked checkouts, including a 163 MB Archify checkout.
| Draft | Previous head | Current head | Call model / adapter revision | Current local evidence |
|---|---|---|---|---|
| #1225 producer | bfb7a5d56986db5222291630ad4f1159c77db317 |
32f10f01c2c0a481419d2a8dfef7d2297a437ec8 |
v1 / 10 | 248 module tests; five source/archive scopes; fresh public replay |
| #1226 saved questions | a16d94f78d2ae6b0b92d2e3785781d7e1944554a |
39ebb6c6d1d967420cfbfd22f35c573cc4145239 |
v1 / 10 | 36 focused tests; six question/archive cases |
| #1227 default imports | c4374a78fd57e27c137673e624e4b3aa4ed7c5ff |
6140a1c82f2830343ffa9fabf19cd17e700dadff |
v2 / 11 | 37 focused tests; three source/archive scopes and three consumer boundaries |
| #1229 paired questions | b6ad7bfc6aaaf47b9f72e8609d96bea008603741 |
3dda561d5db3323b00e8df20627ada96aec95900 |
v2 / 11 | 280 module tests; seven paired/archive cases and two unchanged raw-fact controls |
The v1/v2 contracts are unchanged; adapter revisions identify the corrected extraction behavior. Lower #1219/#1220 heads remain unchanged. All four archives were rebuilt twice identically. The paired layer initially retained an observer test's old revision-9 expectation; it was updated to 11 before the final 280-test pass. Prior Wiki receipts remain evidence for their recorded SHAs. Final-head CI completed for #1225, #1226, #1227 and #1229. Each has the same sole README Classic digest regression verified on exact Labs, with matching expected/actual hashes, plus the aggregate Node22 failure. All other executed jobs passed, including Code Analysis, browser, ZIP and package checks. CI is not green.
Use Git, Node 22, npm and Python 3 in a Unix shell. The script fetches the previous and current producer, checks the candidate archive hash, installs locked dependencies only in temporary analyzer copies and uses four complete pinned target checkouts. Only the inspected authored helpers are executed; target applications are not run. It does not install Archify into a live agent.
Run the fixed producer, runtime and archive comparison
#!/usr/bin/env bash
set -euo pipefail
# Requires Git, Node 22.23.2, npm and unzip; no live installation is modified.
ARCHIFY_REF=${ARCHIFY_REF:-32f10f01c2c0a481419d2a8dfef7d2297a437ec8}
ARCHIFY_ZIP_SHA256=${ARCHIFY_ZIP_SHA256:-9e0516b885517d15f6fe515975f594eb00c17a775b45ec35835e3fb2110bf641}
[[ "$ARCHIFY_REF" =~ ^[0-9a-f]{40}$ ]] || { echo 'ARCHIFY_REF must be a full lowercase commit SHA' >&2; exit 1; }
[[ "$ARCHIFY_ZIP_SHA256" =~ ^[0-9a-f]{64}$ ]] || { echo 'ARCHIFY_ZIP_SHA256 must be lowercase SHA256' >&2; exit 1; }
node -e "if(process.version !== 'v22.23.2') throw Error('Node v22.23.2 required; found '+process.version)"
WORK=$(mktemp -d "${TMPDIR:-/tmp}/archify-default-scope.XXXXXXXX")
echo "Results and checkouts: $WORK"
export EVIDENCE_ROOT="$WORK/results" CASES="$WORK/cases.json"
mkdir -p "$EVIDENCE_ROOT" "$WORK/minimal"
cat > "$WORK/minimal/entry.mjs" <<'ARCHIFY_SOURCE'
import { f } from './target.mjs';
function entry(value = f()) {
function f() { return 'body'; }
return value;
}
console.log(entry());
ARCHIFY_SOURCE
cat > "$WORK/minimal/control.mjs" <<'ARCHIFY_SOURCE'
import { f } from './target.mjs';
function control(value = f()) { return value; }
console.log(control());
ARCHIFY_SOURCE
cat > "$WORK/minimal/target.mjs" <<'ARCHIFY_SOURCE'
export function f() { return 'import'; }
ARCHIFY_SOURCE
cat > "$WORK/public-verify.mjs" <<'ARCHIFY_VERIFY'
import fs from 'node:fs';
import path from 'node:path';
import assert from 'node:assert/strict';
import {spawnSync} from 'node:child_process';
const e=process.env.EVIDENCE_ROOT;
const label=process.argv[2], cli=process.argv[3];
assert.ok(label&&cli,'label and CLI required');
const projects=JSON.parse(fs.readFileSync(process.env.CASES));
const run=(cmd,args)=>{const r=spawnSync(cmd,args,{encoding:'utf8',timeout:240000,maxBuffer:2*1024*1024});assert.equal(r.status,0,r.stderr||r.stdout||String(r.error));return r.stdout;};
const strip=({repository:{adapterRevision,snapshotId,...repository},...facts})=>({repository,...facts});
const result=[];
for(const p of projects){
if(p.revision){assert.equal(run('git',['-C',p.root,'rev-parse','HEAD']).trim(),p.revision);assert.equal(run('git',['-C',p.root,'status','--porcelain','--untracked-files=no']),'');}
const out=path.join(e,`${p.name}-${label}`);
fs.writeFileSync(out+'.receipt.json',run(process.execPath,[cli,'code-analysis','check',p.root,'--language','ts','--fail-on','never','--json','--out',out]));
const raw=fs.readFileSync(out+'/raw-facts.json'),facts=JSON.parse(raw);
assert.equal(facts.files.length,p.files,p.name+': file scope');
assert.equal(facts.imports.length,p.imports,p.name+': import count');
assert.equal(facts.calls.length,p.calls,p.name+': call count');
assert.equal(facts.parse_errors?.length||0,p.errors,p.name+': parse errors');
assert.equal(facts.callModel,'js-ts-lexical-v1');assert.equal(facts.repository.adapterRevision,label==='parent'?8:10);
if(p.name==='minimal'){
for(const file of ['entry.mjs','control.mjs'])assert.equal(run(process.execPath,[path.join(p.root,file)]).trim(),'import');
const call=facts.calls.find(c=>c.file==='entry.mjs'&&c.line===2&&c.column===24);
assert.ok(call);assert.equal(call.resolution,label==='parent'?'same-file-function':'unresolved');
if(label==='parent')assert.deepEqual(call.target,{name:'f',line:3,column:3});else assert.equal(call.target,undefined);
const control=facts.calls.find(c=>c.file==='control.mjs'&&c.line===2);
assert.equal(control.resolution,'imported-function');assert.deepEqual(control.target,{file:'target.mjs',name:'f',line:1,column:1});
}
if(label!=='parent'){
const before=JSON.parse(fs.readFileSync(path.join(e,`${p.name}-parent/raw-facts.json`)));
if(p.name==='minimal')before.calls=before.calls.map(c=>c.file==='entry.mjs'&&c.line===2&&c.column===24?(({target,...rest})=>({...rest,resolution:'unresolved',reason:'no-local-function-binding'}))(c):c);
assert.deepEqual(strip(facts),strip(before),`${p.name}: only expected call/revision/snapshot change`);
}
if(label==='archive')assert.deepEqual(raw,fs.readFileSync(path.join(e,`${p.name}-candidate/raw-facts.json`)));
result.push({name:p.name,pin:p.revision,files:facts.files.length,imports:facts.imports.length,calls:facts.calls.length,parseErrors:facts.parse_errors?.length||0,changedCalls:p.name==='minimal'&&label!=='parent'?1:0,archiveParity:label==='archive'});
console.log(JSON.stringify(result.at(-1)));
}
fs.writeFileSync(path.join(e,label+'-results.json'),JSON.stringify({node:process.version,result},null,2)+'\n');
ARCHIFY_VERIFY
# Execute only the three authored fixture modules.
for name in entry control; do
output=$(node "$WORK/minimal/$name.mjs")
test "$output" = import
echo "$name runtime: $output"
done
fetch_checkout() {
local url=$1 revision=$2 dest=$3 pattern=$4
git init -q "$dest"
git -C "$dest" remote add origin "$url"
git -C "$dest" config http.version HTTP/1.1
if [[ -n "$pattern" ]]; then
git -C "$dest" config core.sparseCheckout true
printf '%s\n' "$pattern" > "$dest/.git/info/sparse-checkout"
fi
git -C "$dest" -c http.version=HTTP/1.1 fetch --quiet --depth=1 --filter=blob:none origin "$revision"
git -C "$dest" checkout -q --detach FETCH_HEAD
test "$(git -C "$dest" rev-parse HEAD)" = "$revision"
}
fetch_checkout https://github.com/tt-a1i/archify.git bfb7a5d56986db5222291630ad4f1159c77db317 "$WORK/parent" '/archify/'
fetch_checkout https://github.com/tt-a1i/archify.git "$ARCHIFY_REF" "$WORK/candidate" $'/archify/\n/archify.zip'
node - "$WORK/candidate/archify.zip" "$ARCHIFY_ZIP_SHA256" <<'JS'
const fs=require('node:fs'),crypto=require('node:crypto'),assert=require('node:assert/strict');
const actual=crypto.createHash('sha256').update(fs.readFileSync(process.argv[2])).digest('hex');
assert.equal(actual,process.argv[3]); console.log('archive SHA256:',actual);
JS
mkdir "$WORK/archive"
unzip -q "$WORK/candidate/archify.zip" -d "$WORK/archive"
for dir in "$WORK/parent/archify/modules/code-analysis" "$WORK/candidate/archify/modules/code-analysis" "$WORK/archive/archify/modules/code-analysis"; do
(cd "$dir" && npm ci --ignore-scripts --no-audit --no-fund)
done
# Complete tracked checkouts preserve the original analysis roots and source scopes.
fetch_checkout https://github.com/tt-a1i/archify.git bf0a29e7664f52973d06a8e6a608df2db1ecbd39 "$WORK/labs" ''
fetch_checkout https://github.com/primus/eventemitter3.git a650549176ded0d0dcc52e27b03ae5669d7dcd7c "$WORK/eventemitter3" ''
fetch_checkout https://github.com/reduxjs/redux.git c9e06506f88926e252daf5275495eba0c04bf8e3 "$WORK/redux" ''
fetch_checkout https://github.com/sverweij/dependency-cruiser.git e6517db02e169f3db5c838bb1565ebd0fefeb452 "$WORK/dependency-cruiser" ''
node - "$WORK" <<'JS'
const fs=require('node:fs'),path=require('node:path'); const w=process.argv[2];
fs.writeFileSync(process.env.CASES,JSON.stringify([
{name:'minimal',root:path.join(w,'minimal'),files:3,imports:2,calls:6,errors:0},
{name:'archify',root:path.join(w,'labs'),revision:'bf0a29e7664f52973d06a8e6a608df2db1ecbd39',files:607,imports:3447,calls:112145,errors:0},
{name:'eventemitter3',root:path.join(w,'eventemitter3'),revision:'a650549176ded0d0dcc52e27b03ae5669d7dcd7c',files:16,imports:87,calls:1085,errors:0},
{name:'redux',root:path.join(w,'redux/src'),revision:'c9e06506f88926e252daf5275495eba0c04bf8e3',files:17,imports:39,calls:87,errors:0},
{name:'dependency-cruiser',root:path.join(w,'dependency-cruiser'),revision:'e6517db02e169f3db5c838bb1565ebd0fefeb452',files:1157,imports:1972,calls:12876,errors:10}
]));
JS
node "$WORK/public-verify.mjs" parent "$WORK/parent/archify/bin/archify.mjs"
node "$WORK/public-verify.mjs" candidate "$WORK/candidate/archify/bin/archify.mjs"
node "$WORK/public-verify.mjs" archive "$WORK/archive/archify/bin/archify.mjs"
echo "Verified; results retained at $WORK"