Skip to content
Open
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
23 changes: 15 additions & 8 deletions internal/cbm/cbm.h
Original file line number Diff line number Diff line change
Expand Up @@ -407,15 +407,22 @@ typedef enum {
CBM_IMPORT_KIND_CONST, // PHP `use const A\B`
} CBMImportKind;

typedef enum {
CBM_REEXPORT_NONE = 0,
CBM_REEXPORT_NAMED,
CBM_REEXPORT_STAR,
} CBMReexportKind;

typedef struct {
const char *local_name; // local alias or name
const char *module_path; // resolved module path / QN
const char *member; // ES named import: the exported name the local binds (`get` in
// `import { get as navigate } from "axios"`); NULL otherwise
bool is_default; // ES default import (`import X from "Y"`), JS/TS only (#1916)
bool type_checking_only; // Python: inside a module-level `if TYPE_CHECKING:` block --
// binds the name for annotations, never at runtime (#1277)
CBMImportKind kind; // CBM_IMPORT_KIND_DEFAULT unless the syntax names a member kind
const char *local_name; // local alias or name
const char *module_path; // resolved module path / QN
const char *member; // ES named import: the exported name the local binds (`get` in
// `import { get as navigate } from "axios"`); NULL otherwise
bool is_default; // ES default import (`import X from "Y"`), JS/TS only (#1916)
bool type_checking_only; // Python: inside a module-level `if TYPE_CHECKING:` block --
// binds the name for annotations, never at runtime (#1277)
CBMImportKind kind; // CBM_IMPORT_KIND_DEFAULT unless the syntax names a member kind
CBMReexportKind reexport; // ES `export {a as b} from` or `export * from`; NONE otherwise
} CBMImport;

/* True when an import binds its local name at runtime. A Python import under
Expand Down
58 changes: 46 additions & 12 deletions internal/cbm/extract_imports.c
Original file line number Diff line number Diff line change
Expand Up @@ -1814,6 +1814,51 @@ static bool process_commonjs_require(CBMExtractCtx *ctx, TSNode call) {
return true;
}

static void process_es_export_statement(CBMExtractCtx *ctx, TSNode node) {
CBMArena *a = ctx->arena;
TSNode src = ts_node_child_by_field_name(node, TS_FIELD("source"));
if (ts_node_is_null(src)) {
return;
}
char *path = strip_quotes(a, cbm_node_text(a, src, ctx->source));
if (!path || !path[0]) {
return;
}
uint32_t nc = ts_node_child_count(node);
for (uint32_t i = 0; i < nc; i++) {
TSNode child = ts_node_child(node, i);
const char *kind = ts_node_type(child);
if (strcmp(kind, "export_clause") == 0) {
uint32_t sc = ts_node_child_count(child);
for (uint32_t j = 0; j < sc; j++) {
TSNode spec = ts_node_child(child, j);
if (strcmp(ts_node_type(spec), "export_specifier") != 0) {
continue;
}
TSNode original = ts_node_child_by_field_name(spec, TS_FIELD("name"));
TSNode alias = ts_node_child_by_field_name(spec, TS_FIELD("alias"));
if (ts_node_is_null(original)) {
continue;
}
const char *member = cbm_node_text(a, original, ctx->source);
const char *exported =
ts_node_is_null(alias) ? member : cbm_node_text(a, alias, ctx->source);
CBMImport imp = {.local_name = exported,
.module_path = path,
.member = member,
.reexport = CBM_REEXPORT_NAMED};
cbm_imports_push(&ctx->result->imports, a, imp);
}
return;
}
if (strcmp(kind, "*") == 0) {
CBMImport imp = {.local_name = "*", .module_path = path, .reexport = CBM_REEXPORT_STAR};
cbm_imports_push(&ctx->result->imports, a, imp);
return;
}
}
}

static void walk_es_imports(CBMExtractCtx *ctx, TSNode root) {
TSNodeStack stack;
ts_nstack_init(&stack, ctx, CBM_SZ_512);
Expand All @@ -1829,18 +1874,7 @@ static void walk_es_imports(CBMExtractCtx *ctx, TSNode root) {
push_children = false;
}
} else if (strcmp(kind, "export_statement") == 0) {
/* Re-export: `export { x } from './mod'` / `export * from './mod'`.
* It carries a `source` string just like an import and creates the
* same module dependency. */
TSNode src = ts_node_child_by_field_name(node, TS_FIELD("source"));
if (!ts_node_is_null(src)) {
char *path = strip_quotes(ctx->arena, cbm_node_text(ctx->arena, src, ctx->source));
if (path && path[0]) {
CBMImport imp = {.local_name = path_last(ctx->arena, path),
.module_path = path};
cbm_imports_push(&ctx->result->imports, ctx->arena, imp);
}
}
process_es_export_statement(ctx, node);
} else if (strcmp(kind, "call_expression") == 0) {
/* CommonJS require() — only consume the node if we recognized
* it as a require call; otherwise keep traversing the children. */
Expand Down
44 changes: 38 additions & 6 deletions src/pipeline/pass_calls.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ enum {
#include <stdint.h>
#include "pipeline/pipeline_internal.h"
#include "pipeline/lsp_resolve.h"
#include "pipeline/pass_lsp_cross.h"
#include "pipeline/laravel_routing.h"
#include "graph_buffer/graph_buffer.h"
#include "foundation/log.h"
Expand Down Expand Up @@ -131,12 +132,36 @@ static const cbm_gbuf_node_t *pc_find_by_computed_qn(const cbm_gbuf_t *gbuf, con
return n;
}

/* The import map of rel_path. *out_vals_owned says who owns the value strings:
* the JS/TS map is cbm_pxc_build_import_map's (values owned), every other
* path borrows its values from the graph buffer. free_import_map takes the
* same flag. */
static int build_import_map(cbm_pipeline_ctx_t *ctx, const char *rel_path,
const CBMFileResult *result, const char ***out_keys,
const char ***out_vals, int *out_count) {
const char ***out_vals, int *out_count, bool *out_vals_owned) {
*out_keys = NULL;
*out_vals = NULL;
*out_count = 0;
*out_vals_owned = false;

/* JS/TS: the cross-file map follows re-export chains to the defining
* file, and every value it holds is the QN of a graph node. */
CBMLanguage lang = cbm_language_for_filename(rel_path);
if (lang == CBM_LANG_JAVASCRIPT || lang == CBM_LANG_TYPESCRIPT || lang == CBM_LANG_TSX) {
const char **keys = NULL;
const char **vals = NULL;
int count = 0;
cbm_pxc_build_import_map(ctx->gbuf, ctx->project_name, rel_path, lang, result, &keys, &vals,
&count);
if (count > 0) {
*out_keys = keys;
*out_vals = vals;
*out_count = count;
*out_vals_owned = true;
return 0;
}
cbm_pxc_free_import_map(keys, vals, count);
}

/* Fast path: build from cached extraction via the same resolver used for
* IMPORTS edges. Do NOT use cbm_pipeline_fqn_module(module_path) — Python
Expand Down Expand Up @@ -225,7 +250,11 @@ static int build_import_map(cbm_pipeline_ctx_t *ctx, const char *rel_path,
return 0;
}

static void free_import_map(const char **keys, const char **vals, int count) {
static void free_import_map(const char **keys, const char **vals, int count, bool vals_owned) {
if (vals_owned) {
cbm_pxc_free_import_map(keys, vals, count);
return;
}
if (keys) {
for (int i = 0; i < count; i++) {
free((void *)keys[i]);
Expand Down Expand Up @@ -1536,7 +1565,8 @@ int cbm_pipeline_pass_calls(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t *file
const char **imp_keys = NULL;
const char **imp_vals = NULL;
int imp_count = 0;
build_import_map(ctx, rel, result, &imp_keys, &imp_vals, &imp_count);
bool imp_vals_owned = false;
build_import_map(ctx, rel, result, &imp_keys, &imp_vals, &imp_count, &imp_vals_owned);
cbm_php_vendor_names_t php_vendor;
cbm_pipeline_php_vendor_names_build(ctx->gbuf, ctx->project_name, rel, result, &php_vendor);

Expand Down Expand Up @@ -1574,7 +1604,7 @@ int cbm_pipeline_pass_calls(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t *file
cbm_free(CBM_MEM_CLASS_RESOLVE, evidence);
free(module_qn);
cbm_pipeline_php_vendor_names_free(&php_vendor);
free_import_map(imp_keys, imp_vals, imp_count);
free_import_map(imp_keys, imp_vals, imp_count, imp_vals_owned);
if (result_owned) {
cbm_free_result(result);
}
Expand Down Expand Up @@ -1710,7 +1740,9 @@ void cbm_pipeline_pass_fastapi_depends(cbm_pipeline_ctx_t *ctx, const cbm_file_i
const char **imp_keys = NULL;
const char **imp_vals = NULL;
int imp_count = 0;
build_import_map(ctx, files[i].rel_path, result, &imp_keys, &imp_vals, &imp_count);
bool imp_vals_owned = false;
build_import_map(ctx, files[i].rel_path, result, &imp_keys, &imp_vals, &imp_count,
&imp_vals_owned);

for (int d = 0; d < result->defs.count; d++) {
CBMDefinition *def = &result->defs.items[d];
Expand All @@ -1729,7 +1761,7 @@ void cbm_pipeline_pass_fastapi_depends(cbm_pipeline_ctx_t *ctx, const cbm_file_i
}

free(module_qn);
free_import_map(imp_keys, imp_vals, imp_count);
free_import_map(imp_keys, imp_vals, imp_count, imp_vals_owned);
free(source);
}

Expand Down
14 changes: 13 additions & 1 deletion src/pipeline/pass_definitions.c
Original file line number Diff line number Diff line change
Expand Up @@ -683,7 +683,19 @@ static int create_import_edges_for_file(cbm_pipeline_ctx_t *ctx, const CBMFileRe
char esc_ln[CBM_SZ_128];
cbm_json_escape(esc_ln, sizeof(esc_ln), imp->local_name ? imp->local_name : "");
char imp_props[CBM_SZ_256];
snprintf(imp_props, sizeof(imp_props), "{\"local_name\":\"%s\"}", esc_ln);
if (imp->reexport == CBM_REEXPORT_NAMED && imp->member) {
char esc_member[CBM_SZ_128];
cbm_json_escape(esc_member, sizeof(esc_member), imp->member);
snprintf(imp_props, sizeof(imp_props),
"{\"local_name\":\"%s\",\"reexport\":\"named\","
"\"imported\":\"%s\"}",
esc_ln, esc_member);
} else if (imp->reexport == CBM_REEXPORT_STAR) {
snprintf(imp_props, sizeof(imp_props),
"{\"local_name\":\"%s\",\"reexport\":\"star\"}", esc_ln);
} else {
snprintf(imp_props, sizeof(imp_props), "{\"local_name\":\"%s\"}", esc_ln);
}
cbm_gbuf_insert_edge(ctx->gbuf, source_node->id, target->id, "IMPORTS", imp_props);
count++;
}
Expand Down
Loading
Loading