fix(javascript): ignore erased TypeScript names in native-addon requests (#2766)

* fix(javascript): ignore erased TypeScript names in native-addon requests

Skip import type / export type declarations and per-binding type
specifiers when collecting .node addon members so only runtime value
imports request native exports (fixes #2667).

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(javascript): cover erased TypeScript re-export forms

* test(javascript): allow Inspector observation events to arrive

* test(inspector): emit events before enable responses

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
This commit is contained in:
authored and GitHub committed 2026-10-14 05:39:09 +08:00
1 parent bee73fcb67
commit 06739aeaf2
4 files changed
+186 -6

No files matched your search

@@ -6,6 +6,12 @@ import {
} from "../unicodeCodePointOrder.js";
import { stripQueryAndFragment } from "../artifactPathSyntax.js";
import type { ElectronNativeAddonBindingFinding } from "./electronStaticAnalysisTypes.js";
import {
isTypeOnlyExportDeclaration,
isTypeOnlyExportSpecifier,
isTypeOnlyImportDeclaration,
isTypeOnlyImportSpecifier,
} from "./javascriptModuleDeclaration.js";
import { addLocatedFinding } from "./javascriptStaticAnalysisFindings.js";
import {
argumentNode,
@@ -47,7 +53,15 @@ const inspectImport = (
context: JavaScriptFindingContext,
): void => {
if (!isNativeSpecifier(node.source.value, "import")) return;
const members = node.specifiers.flatMap((specifier) => {
// TypeScript erases `import type` and per-binding `type` imports; they never
// request a runtime native-addon member.
if (isTypeOnlyImportDeclaration(node)) return;
const valueSpecifiers = node.specifiers.filter(
(specifier) =>
!(t.isImportSpecifier(specifier) && isTypeOnlyImportSpecifier(specifier)),
);
if (node.specifiers.length > 0 && valueSpecifiers.length === 0) return;
const members = valueSpecifiers.flatMap((specifier) => {
if (t.isImportDefaultSpecifier(specifier)) return ["default"];
if (t.isImportNamespaceSpecifier(specifier)) return [];
return [propertyName(specifier.imported)];
@@ -59,7 +73,7 @@ const inspectImport = (
kind: "import",
moduleKind: "import",
members,
namespaceAccess: node.specifiers.some((value) =>
namespaceAccess: valueSpecifiers.some((value) =>
t.isImportNamespaceSpecifier(value),
),
dynamicMemberAccess: false,
@@ -76,7 +90,13 @@ const inspectNamedExport = (
!isNativeSpecifier(node.source.value, "import")
)
return;
const members = node.specifiers.flatMap((specifier) => {
if (isTypeOnlyExportDeclaration(node)) return;
const valueSpecifiers = node.specifiers.filter(
(specifier) =>
!(t.isExportSpecifier(specifier) && isTypeOnlyExportSpecifier(specifier)),
);
if (node.specifiers.length > 0 && valueSpecifiers.length === 0) return;
const members = valueSpecifiers.flatMap((specifier) => {
if (t.isExportSpecifier(specifier)) return [propertyName(specifier.local)];
if (t.isExportDefaultSpecifier(specifier)) return ["default"];
return [];
@@ -88,7 +108,7 @@ const inspectNamedExport = (
kind: "re-export",
moduleKind: "import",
members,
namespaceAccess: node.specifiers.some((value) =>
namespaceAccess: valueSpecifiers.some((value) =>
t.isExportNamespaceSpecifier(value),
),
dynamicMemberAccess: false,
@@ -101,7 +121,8 @@ const inspectExportAll = (
): void => {
if (
node.source === undefined ||
!isNativeSpecifier(node.source.value, "import")
!isNativeSpecifier(node.source.value, "import") ||
isTypeOnlyExportDeclaration(node)
)
return;
addBinding({