mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-06 08:23:19 +08:00
Link the web stream classes for an element-access tee, pipeTo or pipeThrough
The scan that decides whether a program uses a web stream operation the native streams lack only matched property accesses, so a TypeScript program whose one such operation was `body["tee"]()` kept the native streams and threw a TypeError at the call. Element accesses with a string literal key now count the same way, in the TypeScript scan and in the JavaScript one. Adds the corpus program web-streams-typed-element-tee.ts, with its corpus cost and order baseline.
This commit is contained in:
@@ -119,11 +119,8 @@ export function programUsesTypedWebStreams(lowerer: Lowerer): boolean {
|
||||
for (const file of lowerer.program.getSourceFiles()) {
|
||||
if (file.isDeclarationFile || isJsSourceFile(file)) continue;
|
||||
ts.walkPreorder(file, (node) => {
|
||||
if (
|
||||
ts.isPropertyAccessExpression(node) &&
|
||||
WEB_VIEW_CALLS.has(node.name.text) &&
|
||||
isReadableStreamMember(lowerer, node)
|
||||
)
|
||||
const member = staticMemberName(node);
|
||||
if (member !== null && WEB_VIEW_CALLS.has(member) && isReadableStreamMember(lowerer, node))
|
||||
found = true;
|
||||
else if (ts.isNewExpression(node)) {
|
||||
const name = stdlibGlobalNameOf(lowerer, unparenthesized(node.expression));
|
||||
@@ -316,10 +313,29 @@ function isReadableStreamType(lowerer: Lowerer, type: ts.Type): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
/** Whether a member access resolves to a member of the stdlib
|
||||
* ReadableStream interface. */
|
||||
function isReadableStreamMember(lowerer: Lowerer, access: ts.PropertyAccessExpression): boolean {
|
||||
const member = lowerer.checker.getSymbolAtLocation(access.name);
|
||||
type MemberAccess = ts.PropertyAccessExpression | ts.ElementAccessExpression;
|
||||
|
||||
/** The member a property access names, or an element access with a string
|
||||
* literal key (`body.tee` and `body["tee"]`); null for any other node. */
|
||||
function staticMemberName(node: ts.Node): string | null {
|
||||
if (ts.isPropertyAccessExpression(node)) return node.name.text;
|
||||
if (ts.isElementAccessExpression(node)) {
|
||||
const key = node.argumentExpression;
|
||||
if (ts.isStringLiteral(key) || ts.isNoSubstitutionTemplateLiteral(key)) return key.text;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Whether a member access (staticMemberName) resolves to a member of the
|
||||
* stdlib ReadableStream interface. */
|
||||
function isReadableStreamMember(lowerer: Lowerer, access: ts.Node): boolean {
|
||||
const key = ts.isPropertyAccessExpression(access)
|
||||
? access.name
|
||||
: ts.isElementAccessExpression(access)
|
||||
? access.argumentExpression
|
||||
: null;
|
||||
if (key === null) return false;
|
||||
const member = lowerer.checker.getSymbolAtLocation(key);
|
||||
if (member === undefined || !lowerer.isStdlibSymbol(member)) return false;
|
||||
let owner: ts.Node | undefined = member.declarations[0]?.parent;
|
||||
while (owner !== undefined && !ts.isInterfaceDeclaration(owner)) owner = owner.parent;
|
||||
@@ -366,9 +382,10 @@ function programUsesWebStreamOnlyMembers(lowerer: Lowerer, functions: IrFunction
|
||||
let found = false;
|
||||
ts.walkPreorder(file, (node) => {
|
||||
let receiver: ts.Type | null = null;
|
||||
if (ts.isPropertyAccessExpression(node) && members.has(node.name.text)) {
|
||||
const member = staticMemberName(node);
|
||||
if (member !== null && members.has(member)) {
|
||||
if (isReadableStreamMember(lowerer, node)) found = true;
|
||||
else receiver = lowerer.typeOf(node.expression);
|
||||
else receiver = lowerer.typeOf((node as MemberAccess).expression);
|
||||
} else if (iterates && ts.isForOfStatement(node) && node.awaitModifier !== undefined) {
|
||||
receiver = lowerer.typeOf(node.expression);
|
||||
if (isReadableStreamType(lowerer, receiver)) found = true;
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/web-streams-typed-element-tee.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// ReadableStream tee() reached through an element access with a string key
|
||||
// is the program's only web stream operation: the program still links the
|
||||
// web stream classes, so a Response body splits into two branches.
|
||||
|
||||
async function text(stream: ReadableStream<Uint8Array>): Promise<string> {
|
||||
const reader = stream.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let out = "";
|
||||
for (;;) {
|
||||
const result = await reader.read();
|
||||
if (result.done) break;
|
||||
out += decoder.decode(result.value, { stream: true });
|
||||
}
|
||||
return out + decoder.decode();
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const body = new Response("split me").body!;
|
||||
const [left, right] = body["tee"]();
|
||||
console.log("element tee", await text(left), "|", await text(right), body.locked);
|
||||
}
|
||||
|
||||
void main();
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 3000, "san": 6000 }
|
||||
Reference in New Issue
Block a user