mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-07 08:33:50 +08:00
Shadow the export alias only in the scope that declares it again
The export-alias rewrite skipped a whole function when a let or const anywhere inside it declared the alias's name, so the function's other references to the alias kept the alias name after its declaration was removed. A function now shadows the alias only through its parameters, its own name, its vars and the declarations directly in its body; a block, case block, catch clause or for loop shadows it through its own declarations, and the walk skips just that scope.
This commit is contained in:
@@ -62,47 +62,80 @@ function bindsName(name: ts.BindingName | undefined, text: string): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** True when `scope` (a function, or a block or catch clause) declares
|
||||
* `text` itself, so references inside it name that binding instead of the
|
||||
* module-level alias: a parameter, a var/let/const or a function or class
|
||||
* declaration in a function (nested functions excluded), a let/const or
|
||||
* class in a block, a catch binding. */
|
||||
function shadows(scope: ts.Node, text: string): boolean {
|
||||
if (isFunctionScope(scope)) {
|
||||
const fn = scope as ts.SignatureDeclaration;
|
||||
if (fn.parameters.nodes.some((p) => bindsName(p.name, text))) return true;
|
||||
/** True when one of `statements` declares `text` lexically: a let/const,
|
||||
* or a class or function declaration. */
|
||||
function declaresLexically(statements: readonly ts.Statement[], text: string): boolean {
|
||||
for (const stmt of statements) {
|
||||
if (
|
||||
(ts.isFunctionExpression(scope) || ts.isClassExpression(scope)) &&
|
||||
scope.name?.text === text
|
||||
ts.isVariableStatement(stmt) &&
|
||||
(stmt.declarationList.flags & ts.NodeFlags.BlockScoped) !== 0 &&
|
||||
stmt.declarationList.declarations.nodes.some((d) => bindsName(d.name, text))
|
||||
)
|
||||
return true;
|
||||
} else if (ts.isCatchClause(scope)) {
|
||||
if (bindsName(scope.variableDeclaration?.name, text)) return true;
|
||||
} else if (!ts.isBlock(scope)) {
|
||||
return false;
|
||||
if ((ts.isClassDeclaration(stmt) || ts.isFunctionDeclaration(stmt)) && stmt.name?.text === text)
|
||||
return true;
|
||||
}
|
||||
const functionScope = isFunctionScope(scope);
|
||||
return false;
|
||||
}
|
||||
|
||||
/** True when a function body declares `text` with `var` (or a function
|
||||
* declaration in a nested block) anywhere outside nested functions. */
|
||||
function declaresVar(body: ts.Node, text: string): boolean {
|
||||
let found = false;
|
||||
ts.walkPreorder(scope, (n) => {
|
||||
ts.walkPreorder(body, (n) => {
|
||||
if (found) return "stop";
|
||||
if (n === scope) return undefined;
|
||||
if (n === body) return undefined;
|
||||
if (isFunctionScope(n)) {
|
||||
if (ts.isFunctionDeclaration(n) && n.name?.text === text) found = true;
|
||||
return "skip";
|
||||
}
|
||||
if (!functionScope && ts.isBlock(n)) return "skip"; /* its own scope */
|
||||
if (ts.isVariableDeclaration(n) && bindsName(n.name, text)) {
|
||||
const list = n.parent;
|
||||
const blockScoped =
|
||||
ts.isVariableDeclarationList(list) && (list.flags & ts.NodeFlags.BlockScoped) !== 0;
|
||||
if (functionScope || blockScoped) found = true;
|
||||
}
|
||||
if (ts.isClassDeclaration(n) && n.name?.text === text) found = true;
|
||||
if (
|
||||
ts.isVariableDeclarationList(n) &&
|
||||
(n.flags & ts.NodeFlags.BlockScoped) === 0 &&
|
||||
n.declarations.nodes.some((d) => bindsName(d.name, text))
|
||||
)
|
||||
found = true;
|
||||
return undefined;
|
||||
});
|
||||
return found;
|
||||
}
|
||||
|
||||
/** True when `scope` declares `text` itself, so references inside it name
|
||||
* that binding instead of the module-level alias: a function's parameters,
|
||||
* own name, `var`s and the let/const, class and function declarations of
|
||||
* its body; a block's or case block's own let/const, class and function
|
||||
* declarations; a catch binding; a for loop's let/const. A let/const in a
|
||||
* nested block shadows only that block, which the walk reaches as its own
|
||||
* scope. */
|
||||
function shadows(scope: ts.Node, text: string): boolean {
|
||||
if (isFunctionScope(scope)) {
|
||||
const fn = scope as ts.SignatureDeclaration;
|
||||
if (fn.parameters.nodes.some((p) => bindsName(p.name, text))) return true;
|
||||
if (ts.isFunctionExpression(scope) && scope.name?.text === text) return true;
|
||||
const body = (scope as { body?: ts.Node }).body;
|
||||
if (body === undefined || !ts.isBlock(body)) return false;
|
||||
return declaresVar(body, text) || declaresLexically(body.statements.nodes, text);
|
||||
}
|
||||
if (ts.isBlock(scope)) return declaresLexically(scope.statements.nodes, text);
|
||||
if (ts.isCaseBlock(scope))
|
||||
return declaresLexically(
|
||||
scope.clauses.nodes.flatMap((clause) => clause.statements.nodes),
|
||||
text,
|
||||
);
|
||||
if (ts.isCatchClause(scope)) return bindsName(scope.variableDeclaration?.name, text);
|
||||
if (ts.isForStatement(scope) || ts.isForInStatement(scope) || ts.isForOfStatement(scope)) {
|
||||
const init = scope.initializer;
|
||||
return (
|
||||
init !== undefined &&
|
||||
ts.isVariableDeclarationList(init) &&
|
||||
(init.flags & ts.NodeFlags.BlockScoped) !== 0 &&
|
||||
init.declarations.nodes.some((d) => bindsName(d.name, text))
|
||||
);
|
||||
}
|
||||
if (ts.isClassExpression(scope)) return scope.name?.text === text;
|
||||
return false;
|
||||
}
|
||||
|
||||
export function normalizeCjsExportsVar(sf: ts.SourceFile): string | null {
|
||||
const source = sf.text;
|
||||
if (!/\b(?:var|let|const)\s+[A-Za-z_$][\w$]*\s*=\s*exports\s*[;,\n]/.test(source)) return null;
|
||||
@@ -149,7 +182,14 @@ export function normalizeCjsExportsVar(sf: ts.SourceFile): string | null {
|
||||
// that binding, not the export object.
|
||||
if (
|
||||
n !== sf &&
|
||||
(isFunctionScope(n) || ts.isBlock(n) || ts.isCatchClause(n)) &&
|
||||
(isFunctionScope(n) ||
|
||||
ts.isBlock(n) ||
|
||||
ts.isCaseBlock(n) ||
|
||||
ts.isCatchClause(n) ||
|
||||
ts.isForStatement(n) ||
|
||||
ts.isForInStatement(n) ||
|
||||
ts.isForOfStatement(n) ||
|
||||
ts.isClassExpression(n)) &&
|
||||
shadows(n, name)
|
||||
)
|
||||
return "skip";
|
||||
|
||||
@@ -10,3 +10,7 @@ for (const addr of ["10.0.0.1", "192.168.1.20", "127.0.0.1", "::1", "8.8.8.8"])
|
||||
console.log(ip.toLong("192.168.1.1"));
|
||||
// Members whose parameter shadows the alias name.
|
||||
console.log(ip.isV4Format("1.2.3.4"), ip.isV4Format("::1"), ip.normalize(" FE80::1 "));
|
||||
// Members with a block-local `let` or a loop `const` of the alias name:
|
||||
// only that block or loop names the local binding.
|
||||
console.log(ip.classify("FE80::1"), ip.classify("10.1.2.3"), ip.classify("8.8.4.4"));
|
||||
console.log(ip.countPrivate(["127.0.0.1", "10.0.0.2", "10.9.9.9"]), ip.countPrivate(["10.0.0.1"]));
|
||||
|
||||
+2
@@ -4,3 +4,5 @@ export function isPublic(addr: string): boolean;
|
||||
export function toLong(addr: string): number;
|
||||
export function isV4Format(ip: string): boolean;
|
||||
export function normalize(ip: string): string;
|
||||
export function classify(addr: string): string;
|
||||
export function countPrivate(addrs: string[]): number;
|
||||
|
||||
+14
@@ -33,3 +33,17 @@ ip.normalize = function(ip) {
|
||||
var trimmed = ip.trim();
|
||||
return trimmed.toLowerCase();
|
||||
};
|
||||
|
||||
ip.classify = function(addr) {
|
||||
if (addr.indexOf(':') !== -1) {
|
||||
let ip = addr.toLowerCase();
|
||||
return 'v6 ' + ip;
|
||||
}
|
||||
return ip.isPrivate(addr) ? 'private' : 'public';
|
||||
};
|
||||
|
||||
ip.countPrivate = function(addrs) {
|
||||
var n = 0;
|
||||
for (const ip of addrs) if (/^10\./.test(ip)) n++;
|
||||
return n + (ip.isLoopback(addrs[0]) ? 100 : 0);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user