fix(javascript): keep exact empty require paths and request fields

Migrate the remaining `""`-sentinel consumers from #783 to the nullable
`semanticStaticPropertyKey`: CommonJS module origins
(`require("m")['']`), require destructuring (`const {'': a} =
require("m")`), and request body fields (`{ '': value }`). Dynamic keys
still produce no link or field. API selectors that compare against known
names are unchanged.

Fixes #783

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Iwakurakaoru0822 2026-10-06 20:38:11 -07:00
parent f7954a8fd6
commit ad1f6b508f
3 changed files with 55 additions and 10 deletions

View File

@ -55,3 +55,51 @@ it("retains exact empty method names and environment keys", () => {
{ kind: "environment", key: null, resolution: "partial" },
]);
});
it("retains exact empty require paths without dynamic-key links", () => {
const ir = analyzeJavaScriptSemantics(`
const { '': fromPattern } = require("./table.js");
const fromMember = require("./table.js")[''];
const { [key]: dynamicPattern } = require("./table.js");
const dynamicMember = require("./table.js")[key];
module.exports.root = require("./table.js")[''];
`);
expect(
ir.moduleLinks.map(({ kind, importedName, localName, exportedName }) => ({
kind,
importedName,
localName,
exportedName,
})),
).toEqual([
{
kind: "require",
importedName: "",
localName: "fromPattern",
exportedName: null,
},
{
kind: "require",
importedName: "",
localName: "fromMember",
exportedName: null,
},
{
kind: "commonjs-export",
importedName: "",
localName: null,
exportedName: "root",
},
]);
});
it("retains exact empty request field names without dynamic fields", () => {
const ir = analyzeJavaScriptSemantics(`
fetch("https://example.test", { '': value, [key]: 1, body: "x" });
`);
expect(
ir.requestOperations.flatMap(({ fields }) =>
fields.map(({ name }) => name),
),
).toEqual(["", "body"]);
});

View File

@ -405,8 +405,8 @@ const requestFields = (
if (!t.isObjectProperty(property)) return [];
// `{[key]: 1}` sends no knowable field name; reporting `key` would invent
// a request field that may not exist.
const name = semanticStaticPropertyName(property.key, property.computed);
if (name.length === 0) return [];
const name = semanticStaticPropertyKey(property.key, property.computed);
if (name === null) return [];
return [
{
name,

View File

@ -25,10 +25,7 @@ import {
range,
stringValue,
} from "./javascriptStaticAnalysisHelpers.js";
import {
semanticStaticPropertyKey,
semanticStaticPropertyName,
} from "./javascriptAstValues.js";
import { semanticStaticPropertyKey } from "./javascriptAstValues.js";
interface CollectCallableInput {
readonly node: t.Node;
@ -181,8 +178,8 @@ const semanticModuleOrigin = (
const nested = t.isNode(node.object)
? semanticModuleOrigin(node.object, projection, state)
: undefined;
const member = semanticStaticPropertyName(node.property, node.computed);
return nested === undefined || member === ""
const member = semanticStaticPropertyKey(node.property, node.computed);
return nested === undefined || member === null
? undefined
: { ...nested, importedPath: [...nested.importedPath, member] };
}
@ -341,8 +338,8 @@ const requirePatternBindings = (
if (t.isObjectPattern(pattern))
return pattern.properties.flatMap((property) => {
if (t.isRestElement(property)) return [];
const name = semanticStaticPropertyName(property.key, property.computed);
return name === ""
const name = semanticStaticPropertyKey(property.key, property.computed);
return name === null
? []
: requirePatternBindings(property.value, [...path, name]);
});