Revert #35877 - fix receiver on calls of imported and exported functions (#43993)

This commit is contained in:
Ron Buckton
2021-05-07 13:42:12 -07:00
committed by GitHub
parent e5395efe49
commit cb9cd898d1
175 changed files with 347 additions and 509 deletions

View File

@@ -35,8 +35,6 @@ namespace ts {
context.onEmitNode = onEmitNode;
context.enableSubstitution(SyntaxKind.Identifier); // Substitutes expression identifiers with imported/exported symbols.
context.enableSubstitution(SyntaxKind.BinaryExpression); // Substitutes assignments to exported symbols.
context.enableSubstitution(SyntaxKind.CallExpression); // Substitutes expression identifiers with imported/exported symbols.
context.enableSubstitution(SyntaxKind.TaggedTemplateExpression); // Substitutes expression identifiers with imported/exported symbols.
context.enableSubstitution(SyntaxKind.PrefixUnaryExpression); // Substitutes updates to exported symbols.
context.enableSubstitution(SyntaxKind.PostfixUnaryExpression); // Substitutes updates to exported symbols.
context.enableSubstitution(SyntaxKind.ShorthandPropertyAssignment); // Substitutes shorthand property assignments for imported/exported symbols.
@@ -49,7 +47,6 @@ namespace ts {
let currentModuleInfo: ExternalModuleInfo; // The ExternalModuleInfo for the current file.
let noSubstitution: boolean[]; // Set of nodes for which substitution rules should be ignored.
let needUMDDynamicImportHelper: boolean;
let bindingReferenceCache: ESMap<Node, Identifier | SourceFile | ImportClause | ImportSpecifier | undefined> | undefined;
return chainBundle(context, transformSourceFile);
@@ -1746,10 +1743,6 @@ namespace ts {
return substituteExpressionIdentifier(<Identifier>node);
case SyntaxKind.BinaryExpression:
return substituteBinaryExpression(<BinaryExpression>node);
case SyntaxKind.CallExpression:
return substituteCallExpression(<CallExpression>node);
case SyntaxKind.TaggedTemplateExpression:
return substituteTaggedTemplateExpression(<TaggedTemplateExpression>node);
case SyntaxKind.PostfixUnaryExpression:
case SyntaxKind.PrefixUnaryExpression:
return substituteUnaryExpression(<PrefixUnaryExpression | PostfixUnaryExpression>node);
@@ -1758,86 +1751,6 @@ namespace ts {
return node;
}
/**
* For an Identifier, gets the import or export binding that it references.
* @returns One of the following:
* - An `Identifier` if node references an external helpers module (i.e., `tslib`).
* - A `SourceFile` if the node references an export in the file.
* - An `ImportClause` or `ImportSpecifier` if the node references an import binding.
* - Otherwise, `undefined`.
*/
function getImportOrExportBindingReferenceWorker(node: Identifier): Identifier | SourceFile | ImportClause | ImportSpecifier | undefined {
if (getEmitFlags(node) & EmitFlags.HelperName) {
const externalHelpersModuleName = getExternalHelpersModuleName(currentSourceFile);
if (externalHelpersModuleName) {
return externalHelpersModuleName;
}
}
else if (!(isGeneratedIdentifier(node) && !(node.autoGenerateFlags & GeneratedIdentifierFlags.AllowNameSubstitution)) && !isLocalName(node)) {
const exportContainer = resolver.getReferencedExportContainer(node, isExportName(node));
if (exportContainer?.kind === SyntaxKind.SourceFile) {
return exportContainer;
}
const importDeclaration = resolver.getReferencedImportDeclaration(node);
if (importDeclaration && (isImportClause(importDeclaration) || isImportSpecifier(importDeclaration))) {
return importDeclaration;
}
}
return undefined;
}
/**
* For an Identifier, gets the import or export binding that it references.
* @param removeEntry When `false`, the result is cached to avoid recomputing the result in a later substitution.
* When `true`, any cached result for the node is removed.
* @returns One of the following:
* - An `Identifier` if node references an external helpers module (i.e., `tslib`).
* - A `SourceFile` if the node references an export in the file.
* - An `ImportClause` or `ImportSpecifier` if the node references an import binding.
* - Otherwise, `undefined`.
*/
function getImportOrExportBindingReference(node: Identifier, removeEntry: boolean): Identifier | SourceFile | ImportClause | ImportSpecifier | undefined {
let result = bindingReferenceCache?.get(node);
if (!result && !bindingReferenceCache?.has(node)) {
result = getImportOrExportBindingReferenceWorker(node);
if (!removeEntry) {
bindingReferenceCache ||= new Map();
bindingReferenceCache.set(node, result);
}
}
else if (removeEntry) {
bindingReferenceCache?.delete(node);
}
return result;
}
function substituteCallExpression(node: CallExpression) {
if (isIdentifier(node.expression) && getImportOrExportBindingReference(node.expression, /*removeEntry*/ false)) {
return isCallChain(node) ?
factory.updateCallChain(node,
setTextRange(factory.createComma(factory.createNumericLiteral(0), node.expression), node.expression),
node.questionDotToken,
/*typeArguments*/ undefined,
node.arguments) :
factory.updateCallExpression(node,
setTextRange(factory.createComma(factory.createNumericLiteral(0), node.expression), node.expression),
/*typeArguments*/ undefined,
node.arguments);
}
return node;
}
function substituteTaggedTemplateExpression(node: TaggedTemplateExpression) {
if (isIdentifier(node.tag) && getImportOrExportBindingReference(node.tag, /*removeEntry*/ false)) {
return factory.updateTaggedTemplateExpression(
node,
setTextRange(factory.createComma(factory.createNumericLiteral(0), node.tag), node.tag),
/*typeArguments*/ undefined,
node.template);
}
return node;
}
/**
* Substitution for an Identifier expression that may contain an imported or exported
* symbol.
@@ -1845,11 +1758,16 @@ namespace ts {
* @param node The node to substitute.
*/
function substituteExpressionIdentifier(node: Identifier): Expression {
const result = getImportOrExportBindingReference(node, /*removeEntry*/ true);
switch (result?.kind) {
case SyntaxKind.Identifier: // tslib import
return factory.createPropertyAccessExpression(result, node);
case SyntaxKind.SourceFile: // top-level export
if (getEmitFlags(node) & EmitFlags.HelperName) {
const externalHelpersModuleName = getExternalHelpersModuleName(currentSourceFile);
if (externalHelpersModuleName) {
return factory.createPropertyAccessExpression(externalHelpersModuleName, node);
}
return node;
}
else if (!(isGeneratedIdentifier(node) && !(node.autoGenerateFlags & GeneratedIdentifierFlags.AllowNameSubstitution)) && !isLocalName(node)) {
const exportContainer = resolver.getReferencedExportContainer(node, isExportName(node));
if (exportContainer && exportContainer.kind === SyntaxKind.SourceFile) {
return setTextRange(
factory.createPropertyAccessExpression(
factory.createIdentifier("exports"),
@@ -1857,26 +1775,31 @@ namespace ts {
),
/*location*/ node
);
case SyntaxKind.ImportClause:
return setTextRange(
factory.createPropertyAccessExpression(
factory.getGeneratedNameForNode(result.parent),
factory.createIdentifier("default")
),
/*location*/ node
);
case SyntaxKind.ImportSpecifier:
const name = result.propertyName || result.name;
return setTextRange(
factory.createPropertyAccessExpression(
factory.getGeneratedNameForNode(result.parent?.parent?.parent || result),
factory.cloneNode(name)
),
/*location*/ node
);
default:
return node;
}
const importDeclaration = resolver.getReferencedImportDeclaration(node);
if (importDeclaration) {
if (isImportClause(importDeclaration)) {
return setTextRange(
factory.createPropertyAccessExpression(
factory.getGeneratedNameForNode(importDeclaration.parent),
factory.createIdentifier("default")
),
/*location*/ node
);
}
else if (isImportSpecifier(importDeclaration)) {
const name = importDeclaration.propertyName || importDeclaration.name;
return setTextRange(
factory.createPropertyAccessExpression(
factory.getGeneratedNameForNode(importDeclaration.parent?.parent?.parent || importDeclaration),
factory.cloneNode(name)
),
/*location*/ node
);
}
}
}
return node;
}
/**

View File

@@ -92,7 +92,6 @@
"unittests/evaluation/asyncGenerator.ts",
"unittests/evaluation/awaiter.ts",
"unittests/evaluation/destructuring.ts",
"unittests/evaluation/externalModules.ts",
"unittests/evaluation/forAwaitOf.ts",
"unittests/evaluation/forOf.ts",
"unittests/evaluation/optionalCall.ts",

View File

@@ -1,84 +0,0 @@
describe("unittests:: evaluation:: externalModules", () => {
// https://github.com/microsoft/TypeScript/issues/35420
it("Correct 'this' in function exported from external module", async () => {
const result = evaluator.evaluateTypeScript({
files: {
"/.src/output.ts": `
export const output: any[] = [];
`,
"/.src/other.ts": `
import { output } from "./output";
export function f(this: any, expected) {
output.push(this === expected);
}
// 0
f(undefined);
`,
"/.src/main.ts": `
export { output } from "./output";
import { output } from "./output";
import { f } from "./other";
import * as other from "./other";
// 1
f(undefined);
// 2
const obj = {};
f.call(obj, obj);
// 3
other.f(other);
`
},
rootFiles: ["/.src/main.ts"],
main: "/.src/main.ts"
});
assert.equal(result.output[0], true); // `f(undefined)` inside module. Existing behavior is correct.
assert.equal(result.output[1], true); // `f(undefined)` from import. New behavior to match first case.
assert.equal(result.output[2], true); // `f.call(obj, obj)`. Behavior of `.call` (or `.apply`, etc.) should not be affected.
assert.equal(result.output[3], true); // `other.f(other)`. `this` is still namespace because it is left of `.`.
});
it("Correct 'this' in function expression exported from external module", async () => {
const result = evaluator.evaluateTypeScript({
files: {
"/.src/output.ts": `
export const output: any[] = [];
`,
"/.src/other.ts": `
import { output } from "./output";
export const f = function(this: any, expected) {
output.push(this === expected);
}
// 0
f(undefined);
`,
"/.src/main.ts": `
export { output } from "./output";
import { output } from "./output";
import { f } from "./other";
import * as other from "./other";
// 1
f(undefined);
// 2
const obj = {};
f.call(obj, obj);
// 3
other.f(other);
`
},
rootFiles: ["/.src/main.ts"],
main: "/.src/main.ts"
});
assert.equal(result.output[0], true); // `f(undefined)` inside module. Existing behavior is incorrect.
assert.equal(result.output[1], true); // `f(undefined)` from import. New behavior to match first case.
assert.equal(result.output[2], true); // `f.call(obj, obj)`. Behavior of `.call` (or `.apply`, etc.) should not be affected.
assert.equal(result.output[3], true); // `other.f(other)`. `this` is still namespace because it is left of `.`.
});
});

View File

@@ -55,8 +55,8 @@ exports.fn3 = fn3;`;
content: `"use strict";
exports.__esModule = true;${appendJsText === changeJs ? "\nexports.fn3 = void 0;" : ""}
var fns_1 = require("../decls/fns");
(0, fns_1.fn1)();
(0, fns_1.fn2)();
fns_1.fn1();
fns_1.fn2();
${appendJs}`
}];
}