Variant that takes the node's parent DIRECTLY. The callers in * extract_defs.c iterate a known parent's children, so they already * have the parent — passing it here avoids ts_node_parent(node), which * is O(n) per call (tree-sitter nodes carry no parent pointer; the * parent is found by rescanning from the root). On a pathologically * large file (e.g. a 583k-line generated/fixture file with
| 1108 | * thousands of top-level statements) the old per-child ts_node_parent |
| 1109 | * made extraction O(n²) and effectively hung. */ |
| 1110 | bool cbm_is_module_level_p(TSNode parent, CBMLanguage lang) { |
| 1111 | if (ts_node_is_null(parent)) { |
| 1112 | return false; |
| 1113 | } |
| 1114 | const char *pk = ts_node_type(parent); |
| 1115 | |
| 1116 | // Languages with wrapper-pattern (expression_statement/export_statement/assignment_statement) |
| 1117 | if (lang == CBM_LANG_PYTHON) { |
| 1118 | return check_script_module_level(parent, pk, "module", "expression_statement"); |
| 1119 | } |
| 1120 | if (lang == CBM_LANG_JAVASCRIPT || lang == CBM_LANG_TYPESCRIPT || lang == CBM_LANG_TSX) { |
| 1121 | return check_script_module_level(parent, pk, "program", "export_statement"); |
| 1122 | } |
| 1123 | if (lang == CBM_LANG_LUA) { |
| 1124 | return check_script_module_level(parent, pk, "chunk", "assignment_statement"); |
| 1125 | } |
| 1126 | if (lang == CBM_LANG_YAML) { |
| 1127 | return strcmp(pk, "document") == 0 || strcmp(pk, "stream") == 0 || |
| 1128 | strcmp(pk, "block_mapping") == 0; |
| 1129 | } |
| 1130 | |
| 1131 | // Table lookup for the rest |
| 1132 | const char **parents = get_module_parents(lang); |
| 1133 | if (parents) { |
| 1134 | for (const char **p = parents; *p; p++) { |
| 1135 | if (strcmp(pk, *p) == 0) { |
| 1136 | return true; |
| 1137 | } |
| 1138 | } |
| 1139 | } |
| 1140 | return false; |
| 1141 | } |
| 1142 | |
| 1143 | /* Back-compat wrapper: computes the parent via ts_node_parent (O(n)). |
| 1144 | * Prefer cbm_is_module_level_p at call sites that already know the |
no test coverage detected