feat(kernel): R2 — full TypeScript/JavaScript extraction port, byte-parity with the wasm path
Replaces the R1 seed .scm emitter with a bespoke Rust walker (codegraph-kernel/src/tsjs/) that mirrors TreeSitterExtractor's TS/JS paths function-for-function: declarations (incl. #808 field/property classification), qualified names, docstrings (#780 wrapper climbs), signatures, imports/re-exports + per-binding refs, calls with receiver-qualified callees (#1230 literal-receiver skip), instantiations, decorators, inheritance, type annotations (#381), type-alias members + tuple contracts (#359/#634), React component recognition (#841 forwardRef/memo/styled), object-of-functions / zustand-through-middleware / RTK Query endpoints + generated hooks / vuex + pinia store shapes, function-as-value capture with the flush gate (#756), and value-reference edges with the shadow prune (#895/#897). The generic query emitter is deleted — extraction parity needs logic .scm can't express; future languages get walkers too (migration plan §4a). Positions and JS string-slice semantics are emitted in UTF-16 code units natively, so kernel output is byte-identical to web-tree-sitter's — no column diff class exists. Parity evidence (macOS): scripts/kernel-parity.mjs (full-object multiset diff per file) — this repo 353/353 files, excalidraw 643/643 (10,650 nodes / 10,726 edges / 68,307 refs), plus torture fixtures checked into __tests__/fixtures/kernel-parity/ and enforced in npm test by kernel-tsjs-parity.test.ts. The strict compare caught one real decoder bug the loose harness missed: refs must NOT carry denormalized filePath/language at the extractFromSource seam (the store fills them). Perf: extraction 2.6× single-thread on excalidraw (487ms vs 1,255ms, identical outputs). Routing stays opt-in (CODEGRAPH_KERNEL_LANGS) until the R3 equivalence gate (large repo, DB dump-diff, retrieval invariants, agent A/B, Linux/Windows) passes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
c5eebe6beb
commit
9ad5cd7ba2
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* JS-grammar torture fixture (javascript variant: no type machinery, JS class
|
||||
* fields use `field_definition` with a `property` field).
|
||||
*/
|
||||
import { EventEmitter } from 'node:events';
|
||||
const { promisify } = require('node:util');
|
||||
|
||||
/** Legacy prototype-style helper. */
|
||||
function legacyHelper(a, b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
const arrow = (x) => legacyHelper(x, 1);
|
||||
|
||||
class Widget extends EventEmitter {
|
||||
static registry = new Map();
|
||||
#privateField = 1;
|
||||
label = 'w';
|
||||
onTick = () => {
|
||||
this.render();
|
||||
};
|
||||
wrapped = debounce(function () {
|
||||
expensive();
|
||||
}, 50);
|
||||
|
||||
constructor(opts) {
|
||||
super();
|
||||
this.opts = opts;
|
||||
register(this.onTick);
|
||||
}
|
||||
|
||||
render() {
|
||||
paint(this.label);
|
||||
}
|
||||
|
||||
static create(opts) {
|
||||
return new Widget(opts);
|
||||
}
|
||||
}
|
||||
|
||||
// AMD-style wrapper — anonymous, but inner functions must still surface.
|
||||
(function () {
|
||||
function hiddenInner() {
|
||||
return 7;
|
||||
}
|
||||
hiddenInner();
|
||||
})();
|
||||
|
||||
module.exports.makeWidget = function makeWidget(opts) {
|
||||
return Widget.create(opts);
|
||||
};
|
||||
|
||||
// Vuex module shape (store-file signals: mutations + actions + getters).
|
||||
const mutations = {
|
||||
SET_USER(state, user) {
|
||||
state.user = user;
|
||||
},
|
||||
};
|
||||
const actions = {
|
||||
async loadUser({ commit }, id) {
|
||||
const user = await fetchUser(id);
|
||||
commit('SET_USER', user);
|
||||
},
|
||||
};
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: () => ({ user: null }),
|
||||
mutations,
|
||||
actions,
|
||||
getters: {
|
||||
userName(state) {
|
||||
return state.user?.name;
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,214 @@
|
||||
/**
|
||||
* Torture fixture — exercises every TS/TSX extraction path the kernel ports.
|
||||
*/
|
||||
import React, { forwardRef, memo, useState as useStateAlias } from 'react';
|
||||
import * as NS from './namespace-module';
|
||||
import DefaultThing from './default-module';
|
||||
import './side-effect';
|
||||
import { helperFn, CONFIG_TABLE } from './helpers';
|
||||
|
||||
export { reExported, orig as aliased } from './barrel-source';
|
||||
export * from './star-source';
|
||||
|
||||
// A documented constant table (value-ref target).
|
||||
const RETRY_LIMITS = { a: 1, b: 2 };
|
||||
export const API_BASE = 'https://example.test';
|
||||
let plainLet = 42;
|
||||
var oldVar = 'x';
|
||||
|
||||
/** Class docs.
|
||||
* Multi-line.
|
||||
*/
|
||||
@Injectable()
|
||||
@scoped.Registry<Config>('name')
|
||||
export abstract class BaseService extends EventTarget implements Disposable, Serializable {
|
||||
static instances = 0;
|
||||
private readonly cache: Map<string, Config> = new Map();
|
||||
public fonts: FontConfig;
|
||||
count = 0;
|
||||
onScroll = throttle((e: Event) => {
|
||||
this.handleScroll(e);
|
||||
}, 100);
|
||||
handleClick = (ev: MouseEvent): void => {
|
||||
emitTelemetry(ev);
|
||||
new AbortController();
|
||||
};
|
||||
|
||||
@Get('/list')
|
||||
async list(query: QueryOpts): Promise<Result<Item>> {
|
||||
const local: ItemMapper = makeMapper();
|
||||
const limit = RETRY_LIMITS;
|
||||
return this.cache.get(query.key) ?? helperFn(query);
|
||||
}
|
||||
|
||||
private static compute(n: number): number {
|
||||
return n * RETRY_LIMITS.a;
|
||||
}
|
||||
|
||||
get size(): number {
|
||||
return this.count;
|
||||
}
|
||||
|
||||
protected dispose(): void {
|
||||
listeners.forEach((l) => unregister(l));
|
||||
}
|
||||
}
|
||||
|
||||
class Plain {
|
||||
constructor(private svc: BaseService) {
|
||||
register(this.onEvent);
|
||||
btn.on('click', this.handleClick);
|
||||
}
|
||||
onEvent() {}
|
||||
handleClick() {}
|
||||
}
|
||||
|
||||
export interface Shape extends Named, Sized {
|
||||
area: number;
|
||||
resize(f: number): Shape;
|
||||
onchange: (s: Shape) => void;
|
||||
}
|
||||
|
||||
export enum Direction {
|
||||
Up,
|
||||
Down = 2,
|
||||
Left,
|
||||
}
|
||||
|
||||
export type Handle = {
|
||||
stop: () => void;
|
||||
id: string;
|
||||
refresh(force: boolean): Promise<void>;
|
||||
};
|
||||
|
||||
export type ServiceList = [
|
||||
Service<'query_apply_record', Req, Resp>,
|
||||
Service<'apply_confirm', Req, Resp>,
|
||||
];
|
||||
|
||||
export type MaybeShape = Shape | null;
|
||||
|
||||
export function topLevel(a: ShapeConfig): Shape {
|
||||
const inner = () => reallyDeep();
|
||||
function namedInner(): void {
|
||||
chained(a).value;
|
||||
}
|
||||
namedInner();
|
||||
return makeShape(a);
|
||||
}
|
||||
|
||||
async function* generatorFn(items: Item[]) {
|
||||
yield* items.map((i) => transform(i));
|
||||
}
|
||||
|
||||
export const arrowConst = async (x: number) => {
|
||||
return x + plainLet;
|
||||
};
|
||||
|
||||
const AnonClassHolder = class {
|
||||
method() {}
|
||||
};
|
||||
|
||||
// React components (#841).
|
||||
export const Button = forwardRef((props: ButtonProps, ref) => {
|
||||
const [state, setState] = useStateAlias(0);
|
||||
useEffect(() => {
|
||||
trackRender();
|
||||
});
|
||||
return <BaseButton ref={ref} onClick={() => setState(state + 1)} {...props} />;
|
||||
});
|
||||
|
||||
export const MemoRow = memo(function Row(props: RowProps) {
|
||||
return <tr className={props.cls}>{props.children}</tr>;
|
||||
});
|
||||
|
||||
export const Wrapped = React.memo(ImportedComponent);
|
||||
export const Styled = styled.button`
|
||||
color: red;
|
||||
`;
|
||||
const memoCache = memo(computeThing); // lowercase — stays a constant
|
||||
|
||||
export function App() {
|
||||
const nodes: GraphNode[] = [];
|
||||
return (
|
||||
<div>
|
||||
<Button label={CONFIG_TABLE.label} />
|
||||
<NS.Panel />
|
||||
{nodes.map((n) => (
|
||||
<MemoRow key={n.id} cls={n.cls} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Object-of-functions (SvelteKit-style actions map).
|
||||
export const actionsMap = {
|
||||
create: async (input: CreateInput) => {
|
||||
return persistNew(input);
|
||||
},
|
||||
update(input: UpdateInput) {
|
||||
return persistExisting(input);
|
||||
},
|
||||
};
|
||||
|
||||
// Zustand-style store through middleware wrappers.
|
||||
export const useStore = create(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
fetchUser: async (id: string) => {
|
||||
const user = await api.load(id);
|
||||
set({ user });
|
||||
},
|
||||
reset: () => set({ user: null }),
|
||||
}),
|
||||
{ name: 'store' }
|
||||
)
|
||||
);
|
||||
|
||||
// RTK Query.
|
||||
export const widgetApi = createApi({
|
||||
reducerPath: 'widgets',
|
||||
endpoints: (build) => ({
|
||||
getWidget: build.query({
|
||||
query: (id: string) => fetchWidget(id),
|
||||
}),
|
||||
updateWidget: build.mutation({
|
||||
queryFn: async (patch) => {
|
||||
return applyPatch(patch);
|
||||
},
|
||||
}),
|
||||
prebuilt: build.query(makeEndpointConfig()),
|
||||
}),
|
||||
});
|
||||
|
||||
export const { useGetWidgetQuery, useUpdateWidgetMutation } = widgetApi;
|
||||
const { notAHook } = widgetApi;
|
||||
|
||||
// Value-ref shadowing: SHADOWED_LIMIT re-bound locally must be pruned.
|
||||
const SHADOWED_LIMIT = 10;
|
||||
export function readsShadowed() {
|
||||
const SHADOWED_LIMIT = 20;
|
||||
return SHADOWED_LIMIT;
|
||||
}
|
||||
export function readsTable() {
|
||||
return RETRY_LIMITS.b + API_BASE.length;
|
||||
}
|
||||
|
||||
// Fn-ref registrations.
|
||||
registerHandler(helperFn);
|
||||
queueMicrotask(topLevel);
|
||||
const routeTable = { home: topLevel, missing: notDefinedAnywhere };
|
||||
const handlerList = [helperFn, DefaultThing];
|
||||
target.cb = topLevel;
|
||||
const aliasFn = topLevel;
|
||||
|
||||
// Calls with interesting callees.
|
||||
;(topLevel)(cfg);
|
||||
NS.helper.deep(1);
|
||||
"literal".includes('x');
|
||||
[1, 2].map(String);
|
||||
obj?.optMethod?.(3);
|
||||
import('./dynamic-module');
|
||||
new NS.Widget(makeArg());
|
||||
new Map<string, number>();
|
||||
super_weird?.();
|
||||
Reference in New Issue
Block a user