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
1 change: 1 addition & 0 deletions graph/bundle/SCHEMA.md
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,7 @@ THE GRAPH. One row per (site, resolved target). A site with N possible targets h
| `DYNAMIC_CODE_CALL` | javascript | `eval(…)` / `new Function(…)` — unknowable by construction. |
| `DYNAMIC_IMPORT_CALL` | javascript | `import(…)` — a module load that is also a site. |
| `JSX_ELEMENT` | javascript | `<Component …/>` — the renderer runs the component (a function component, or a class component's constructor and `render`) with the element's attributes as its props. Engine-authored: no written call; the site is the JSX element expression. An intrinsic tag (`<div/>`) is no site. |
| `JSX_ATTRIBUTE` | javascript | `<form onSubmit={submit}>`, `<UserForm action={create}/>` — a function handed over as a JSX attribute, on any tag; always tier callback_registered. Engine-authored: the site is the attribute. |
| `SIMPLE_CALL` | python | `f(…)` — a bare name. |
| `METHOD_CALL` | python | `obj.m(…)`. |
| `CHAINED_CALL` | python | `a.b().c(…)` — the receiver is itself a call. |
Expand Down
1 change: 1 addition & 0 deletions graph/bundle/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,7 @@ export const VOCAB: readonly VocabSpec[] = [
{ table: 'call_edges', column: 'kind', value: 'DYNAMIC_CODE_CALL', languages: S, meaning: '`eval(…)` / `new Function(…)` — unknowable by construction.' },
{ table: 'call_edges', column: 'kind', value: 'DYNAMIC_IMPORT_CALL', languages: S, meaning: '`import(…)` — a module load that is also a site.' },
{ table: 'call_edges', column: 'kind', value: 'JSX_ELEMENT', languages: S, meaning: '`<Component …/>` — the renderer runs the component (a function component, or a class component\'s constructor and `render`) with the element\'s attributes as its props. Engine-authored: no written call; the site is the JSX element expression. An intrinsic tag (`<div/>`) is no site.' },
{ table: 'call_edges', column: 'kind', value: 'JSX_ATTRIBUTE', languages: S, meaning: '`<form onSubmit={submit}>`, `<UserForm action={create}/>` — a function handed over as a JSX attribute, on any tag; always tier callback_registered. Engine-authored: the site is the attribute.' },
// — Python (the parser's callKind, plus engine-authored decorator/metaclass forms)
{ table: 'call_edges', column: 'kind', value: 'SIMPLE_CALL', languages: P, meaning: '`f(…)` — a bare name.' },
{ table: 'call_edges', column: 'kind', value: 'METHOD_CALL', languages: P, meaning: '`obj.m(…)`.' },
Expand Down
17 changes: 17 additions & 0 deletions graph/javascript/engine/call-edge-generation/jsx.dl
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,20 @@ call_chain_edge(je, caller, "-", m, "lib", "boundary_lib", "JSX_ELEMENT") :- jsx
// A tag that names something the value layer could not follow: a declared unknown.
call_chain_edge(je, caller, "-", "-", "-", "ambiguous_unknown", "JSX_ELEMENT") :-
jsx_target_count(je, 0), jsx_caller(je, caller).

// ── a function handed over as an attribute ──────────────────────────────────
// `<form onSubmit={submit}>`, `<button onClick={logout}>`, `<UserForm action={createUser}/>`
// hands the function to the renderer or the component, which may invoke it: the same
// hand-off as `xs.forEach(f)`, and labelled the same way, callback_registered. Without
// it an event handler on an intrinsic tag (`<div/>` renders no project code, so the
// props object above reaches nobody) had no incoming edge at all, and `impact submit`
// answered that it is used only where it is declared. Emitted for every element,
// whatever its tag renders, as a call argument is whatever the callee resolved to.
// The site is the attribute row; a value that is not a function (`value={count}`,
// `className="x"`) is data and gets nothing.
jsx_attribute_handler(a, m) :- expr_kind(_, "JSX_ATTRIBUTE_VALUE", _, a),
expr_child(_, a, "PROPERTY_VALUE", _, v), expr_value(v, "func", m).
jsx_attribute_caller(a, m) :- jsx_attribute_handler(a, _), expr_owner(_, m, _, a), m != "".
jsx_attribute_caller(a, init) :- jsx_attribute_handler(a, _), expr_owner(_, "", mod, a), module_init(_, init, mod).
call_chain_edge(a, caller, "-", m, "client", "callback_registered", "JSX_ATTRIBUTE") :-
jsx_attribute_handler(a, m), method_prov(m, "client"), jsx_attribute_caller(a, caller).
2 changes: 2 additions & 0 deletions graph/javascript/souffle/decls_all.dl
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
.decl jsx_caller(c0:symbol, c1:symbol)
.decl jsx_target_count(c0:symbol, c1:number)
.decl jsx_class(c0:symbol, c1:symbol)
.decl jsx_attribute_handler(c0:symbol, c1:symbol)
.decl jsx_attribute_caller(c0:symbol, c1:symbol)
.decl jsx_wrapper_name(c0:symbol)
.decl jsx_component_wrapper(c0:symbol)
.decl jsx_loader_name(c0:symbol)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { createUser } from './actions';
import UserForm from './UserForm';

function logout() { return 'out'; }
function label() { return 'Save'; }

export default function LoginForm({ count }) {
const submit = (e) => { e.preventDefault(); return 'in'; };
const settings = { theme: 'dark' };
return (
<form onSubmit={submit}>
<button type="button" onClick={logout}>out</button>
<input value={count} className="field" title={label()} data-settings={settings} />
<span onMouseEnter={() => label()}>hover</span>
<UserForm action={createUser} />
</form>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function UserForm({ action }) {
return <form action={action}><button type="submit">go</button></form>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export async function createUser(data) { return data; }
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import LoginForm from './LoginForm';
export const root = <LoginForm count={1} />;
3 changes: 3 additions & 0 deletions graph/test/javascript/expected/62-jsx-component-render.edges
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ App.jsx:20:27 FUNCTION_CALL format -> known_edge App.jsx:26:1 format
App.jsx:20:7 JSX_ELEMENT <ui.List>{(item) => format(item)}</ui.List> -> known_edge components/ui.jsx:3:1 List
App.jsx:25:30 FUNCTION_CALL div -> known_edge App.jsx:7:1 div
App.jsx:26:32 FUNCTION_CALL String -> ambient_terminal -
components/Panel.jsx:5:21 JSX_ATTRIBUTE onClick={() => this.props.onClose()} -> callback_registered components/Panel.jsx:5:30 <arrow>
components/Panel.jsx:5:36 METHOD_CALL this.props.onClose -> known_edge App.jsx:25:1 closeAll
components/TodoItem.jsx:3:14 JSX_ATTRIBUTE onClick={() => onToggle(todo)} -> callback_registered components/TodoItem.jsx:3:23 <arrow>
components/TodoItem.jsx:3:29 FUNCTION_CALL onToggle -> known_edge components/TodoList.jsx:4:3 handleToggle
components/TodoList.jsx:4:40 FUNCTION_CALL save -> known_edge components/TodoList.jsx:9:1 save
components/TodoList.jsx:6:15 METHOD_CALL todos.map -> ambient_terminal -
components/TodoList.jsx:6:15 METHOD_CALL todos.map -> callback_registered components/TodoList.jsx:6:25 <arrow>
components/TodoList.jsx:6:32 JSX_ELEMENT <TodoItem key={t.text} todo={t} onToggle={handleToggle} /> -> known_edge components/TodoItem.jsx:2:1 TodoItem
components/TodoList.jsx:6:64 JSX_ATTRIBUTE onToggle={handleToggle} -> callback_registered components/TodoList.jsx:4:3 handleToggle
components/ui.jsx:3:43 METHOD_CALL [1, 2].map -> ambient_terminal -
components/ui.jsx:3:43 METHOD_CALL [1, 2].map -> callback_registered components/ui.jsx:3:54 <arrow>
components/ui.jsx:3:61 METHOD_CALL props.children -> known_edge App.jsx:20:17 <arrow>
Expand Down
2 changes: 2 additions & 0 deletions graph/test/javascript/expected/70-jsx-attribute-handler.diag
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
package_entry @axiomcode/code-graph . [] MAIN dist/reason.js NOT_STAGED -> -
unresolved LoginForm.jsx:8:27 METHOD_CALL preventDefault receiver_untyped
10 changes: 10 additions & 0 deletions graph/test/javascript/expected/70-jsx-attribute-handler.edges
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
LoginForm.jsx:11:11 JSX_ATTRIBUTE onSubmit={submit} -> callback_registered LoginForm.jsx:8:18 <arrow>
LoginForm.jsx:12:29 JSX_ATTRIBUTE onClick={logout} -> callback_registered LoginForm.jsx:4:1 logout
LoginForm.jsx:13:53 FUNCTION_CALL label -> known_edge LoginForm.jsx:5:1 label
LoginForm.jsx:14:13 JSX_ATTRIBUTE onMouseEnter={() => label()} -> callback_registered LoginForm.jsx:14:27 <arrow>
LoginForm.jsx:14:33 FUNCTION_CALL label -> known_edge LoginForm.jsx:5:1 label
LoginForm.jsx:15:17 JSX_ATTRIBUTE action={createUser} -> callback_registered actions.js:1:1 createUser
LoginForm.jsx:15:7 JSX_ELEMENT <UserForm action={createUser} /> -> known_edge UserForm.jsx:1:1 UserForm
LoginForm.jsx:8:27 METHOD_CALL e.preventDefault -> ambiguous_unknown -
UserForm.jsx:2:16 JSX_ATTRIBUTE action={action} -> callback_registered actions.js:1:1 createUser
main.jsx:2:21 JSX_ELEMENT <LoginForm count={1} /> -> known_edge LoginForm.jsx:7:1 LoginForm
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
LoginForm.jsx:13:53 FUNCTION_CALL label EXACT LoginForm.jsx:5:1
LoginForm.jsx:14:33 FUNCTION_CALL label EXACT LoginForm.jsx:5:1
# defects: 0
6 changes: 4 additions & 2 deletions graph/test/javascript/tools/normalize_edges.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,10 @@ def methods(d, tag):
if h:
i = {c: h.index(c) for c in ('jsExpressionUniqueHash', 'ownerModuleLinkHash', 'startLine', 'startColumn', 'expressionKind', 'text')}
for r in exprs:
# A JSX element edge is keyed on the JSX_ELEMENT expression, likewise no call site.
if r[i['expressionKind']] in ('PROPERTY_ACCESS', 'OPTIONAL_ACCESS', 'ELEMENT_ACCESS', 'JSX_ELEMENT'):
# A JSX element edge is keyed on the JSX_ELEMENT expression, likewise no call site;
# a handler handed over as an attribute, on the JSX_ATTRIBUTE_VALUE expression.
if r[i['expressionKind']] in ('PROPERTY_ACCESS', 'OPTIONAL_ACCESS', 'ELEMENT_ACCESS', 'JSX_ELEMENT',
'JSX_ATTRIBUTE_VALUE'):
access[r[i['jsExpressionUniqueHash']]] = '%s:%s:%s %%s %s' % (
modfile.get(r[i['ownerModuleLinkHash']], '?'), r[i['startLine']], r[i['startColumn']],
r[i['text']].replace('\n', ' ')[:60])
Expand Down
Loading