mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-28 06:53:42 +08:00
Look up exports whose names start with "__" by their escaped key
TypeScript keys a symbol table entry whose name starts with `__` with an
extra `_` (`__x` is stored as `___x`; `symbolName` removes it). Native ESM
link checking looked an import up by its own spelling, so a program that
imports such an export compiled to Node's startup SyntaxError:
// dep.ts
export const __x = 1;
// main.ts
import { __x } from "./dep.ts"; // The requested module './dep.ts'
// does not provide an export named '__x'
`escapedSymbolName`, the inverse of `symbolName`, gives a name's table key.
Link checking, external type lookups, CommonJS export lookups and the
`this` check of ES module exports now look names up through it. `Object.keys`
over a module namespace skipped every key starting with `__` to drop
TypeScript's internal names, so it also dropped these exports; it now
skips only internal names and lists exports by their own names.
Closes #1582.
Co-authored-by: thekevinbrown <1531750+thekevinbrown@users.noreply.github.com>
This commit is contained in:
co-authored by
thekevinbrown
parent
850710fbea
commit
19d6736b01
@@ -381,7 +381,7 @@ function esModuleExportIgnoresThis(
|
||||
depth: number,
|
||||
): boolean {
|
||||
const moduleSym = lowerer.checker.getSymbolAtLocation(sf);
|
||||
const exported = moduleSym?.getExports().get(name as ts.__String);
|
||||
const exported = moduleSym?.getExports().get(ts.escapedSymbolName(name));
|
||||
if (exported !== undefined) {
|
||||
const target =
|
||||
exported.flags & ts.SymbolFlags.Alias ? lowerer.checker.getAliasedSymbol(exported) : exported;
|
||||
|
||||
@@ -8777,11 +8777,13 @@ function lowerObjectStaticCall(
|
||||
const moduleSymbol = source ? lowerer.checker.getSymbolAtLocation(source) : undefined;
|
||||
const names: string[] = [];
|
||||
moduleSymbol?.getExports().forEach((symbol, key) => {
|
||||
// Internal names (`__export`) start with `__`; an exported name that
|
||||
// does is keyed with an extra `_` (escapedSymbolName).
|
||||
const name = String(key);
|
||||
if (name.startsWith("__") || name === "export=") return;
|
||||
if ((name.startsWith("__") && !name.startsWith("___")) || name === "export=") return;
|
||||
const target =
|
||||
symbol.flags & ts.SymbolFlags.Alias ? lowerer.checker.getAliasedSymbol(symbol) : symbol;
|
||||
if (target.flags & ts.SymbolFlags.Value) names.push(name);
|
||||
if (target.flags & ts.SymbolFlags.Value) names.push(ts.symbolName(symbol));
|
||||
});
|
||||
names.sort();
|
||||
const loc = locOf(call);
|
||||
|
||||
@@ -4219,7 +4219,7 @@ export class Lowerer {
|
||||
// exact mappings, while star exports resolve to the mapped declaration
|
||||
// owner through externalTypeSpecifiersByFile.
|
||||
const moduleSymbol = this.checker.getSymbolAtLocation(sf);
|
||||
const exported = moduleSymbol?.getExports().get(exportName as ts.__String);
|
||||
const exported = moduleSymbol?.getExports().get(ts.escapedSymbolName(exportName));
|
||||
return this.externalTypeSpecifierOfSymbol(exported, seenSymbols, seenExports);
|
||||
}
|
||||
|
||||
@@ -4362,7 +4362,7 @@ export class Lowerer {
|
||||
cjsModuleExportSymbol(sf: ts.SourceFile, name: string): ts.Symbol | undefined {
|
||||
const moduleSym = this.checker.getSymbolAtLocation(sf);
|
||||
const exports = moduleSym?.getExports();
|
||||
const direct = exports?.get(name as ts.__String);
|
||||
const direct = exports?.get(ts.escapedSymbolName(name));
|
||||
if (direct) return direct;
|
||||
// tsgo represents `module.exports = { ... }` as one `export=` symbol
|
||||
// and does not duplicate the object literal's properties into the
|
||||
|
||||
@@ -6437,7 +6437,7 @@ function analyzeEsmNamedImportLinks(
|
||||
const nextSeen = new Set(seenModules);
|
||||
nextSeen.add(dep);
|
||||
const module = checker.getSymbolAtLocation(dep);
|
||||
const exported = module?.getExports().get(name as ts.__String);
|
||||
const exported = module?.getExports().get(ts.escapedSymbolName(name));
|
||||
if (exported !== undefined) {
|
||||
let resolved = exported;
|
||||
const seenSymbols = new Set<ts.Symbol>();
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
SemanticSnapshot,
|
||||
SemanticType,
|
||||
symbolNameOf,
|
||||
escapedSymbolName,
|
||||
} from "./semantic-model.js";
|
||||
import type { __String } from "./ast-types.js";
|
||||
import {
|
||||
@@ -313,6 +314,11 @@ test("symbol tables keep escaped keys, display names, canonical context and cach
|
||||
const exports = parent.getExports();
|
||||
expect(symbolNameOf(members.get("___x" as __String))).toBe("__x");
|
||||
expect(symbolNameOf(exports.get("__x" as __String))).toBe("__x");
|
||||
// A name that starts with `__` is keyed with an extra `_`.
|
||||
expect(symbolNameOf(members.get(escapedSymbolName("__x")))).toBe("__x");
|
||||
expect(escapedSymbolName("x")).toBe("x");
|
||||
expect(escapedSymbolName("_x")).toBe("_x");
|
||||
expect(escapedSymbolName("___y")).toBe("____y");
|
||||
expect(parent.getMembers()).toBe(members);
|
||||
expect(parent.getExports()).toBe(exports);
|
||||
expect(parent.getParent()).toBe(first.symbol(symbol(2)));
|
||||
|
||||
@@ -454,6 +454,12 @@ export function symbolName(symbol: SemanticSymbol): string {
|
||||
return name.startsWith("___") ? name.slice(1) : name;
|
||||
}
|
||||
|
||||
/** The key of the name `name` in a symbol table (`getExports()`, `getMembers()`): the name, with
|
||||
* an extra `_` when it starts with `__`. The inverse of `symbolName`. */
|
||||
export function escapedSymbolName(name: string): __String {
|
||||
return (name.startsWith("__") ? `_${name}` : name) as __String;
|
||||
}
|
||||
|
||||
/** `symbolName(symbol)`, or undefined without a symbol. */
|
||||
export function symbolNameOf(symbol: SemanticSymbol | null | undefined): string | undefined {
|
||||
return symbol === undefined || symbol === null ? undefined : symbolName(symbol);
|
||||
|
||||
@@ -30,7 +30,7 @@ export type {
|
||||
SemanticSignature as Signature,
|
||||
SemanticSymbol as Symbol,
|
||||
} from "./semantic-model.js";
|
||||
export { symbolName, symbolNameOf } from "./semantic-model.js";
|
||||
export { escapedSymbolName, symbolName, symbolNameOf } from "./semantic-model.js";
|
||||
export type {
|
||||
SemanticIndexInfo as IndexInfo,
|
||||
SemanticTypePredicate as TypePredicate,
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7306-dunder-exports/dep.ts",
|
||||
"<repo>/tests/corpus/7306-dunder-exports/star.ts",
|
||||
"<repo>/tests/corpus/7306-dunder-exports/facade.ts",
|
||||
"<repo>/tests/corpus/7306-dunder-exports/table.cjs",
|
||||
"<repo>/tests/corpus/7306-dunder-exports/main.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
export const __x = 1;
|
||||
export const ___y = "three";
|
||||
export let __count = 0;
|
||||
export function __bump(): number {
|
||||
__count++;
|
||||
return __count;
|
||||
}
|
||||
export class __Point {
|
||||
a: number;
|
||||
b: number;
|
||||
constructor(a: number, b: number) {
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
}
|
||||
sum(): number {
|
||||
return this.a + this.b;
|
||||
}
|
||||
}
|
||||
export const plain = "plain";
|
||||
@@ -0,0 +1,2 @@
|
||||
export { __x, __x as __alias, ___y as renamed } from "./dep.ts";
|
||||
export * from "./star.ts";
|
||||
@@ -0,0 +1,20 @@
|
||||
// Exports whose names start with "__": named imports of a const, a function,
|
||||
// a class and a live let, a name with three underscores, aliased re-exports,
|
||||
// names reached through `export *`, namespace member reads and keys, and
|
||||
// CommonJS exports read through require.
|
||||
import { __x, ___y, __count, __bump, __Point, plain } from "./dep.ts";
|
||||
import { __alias, renamed, __starred, __twice } from "./facade.ts";
|
||||
import * as facade from "./facade.ts";
|
||||
import * as depNs from "./dep.ts";
|
||||
import { createRequire } from "node:module";
|
||||
|
||||
console.log(__x, ___y, plain);
|
||||
console.log(__count, __bump(), __bump(), __count);
|
||||
console.log(new __Point(2, 3).sum());
|
||||
console.log(__alias, renamed, __starred, __twice(21));
|
||||
console.log(facade.__x, facade.__starred, facade.__twice(2));
|
||||
console.log(Object.keys(depNs).join(","));
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const table = require("./table.cjs");
|
||||
console.log(table.__a, table.__hello("dunder"), table.b);
|
||||
@@ -0,0 +1,4 @@
|
||||
export const __starred = "from export *";
|
||||
export function __twice(n: number): number {
|
||||
return n * 2;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
exports.__a = 10;
|
||||
exports.__hello = function (name) {
|
||||
return "hello " + name;
|
||||
};
|
||||
exports.b = 20;
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 2400, "san": 3600 }
|
||||
Reference in New Issue
Block a user