Fix and validate post-increment/decrement in module emit (#44968)

This commit is contained in:
Ron Buckton
2021-07-19 13:34:42 -07:00
committed by GitHub
parent 476054ea57
commit 365b25693c
13 changed files with 857 additions and 310 deletions

View File

@@ -2724,6 +2724,14 @@ namespace ts {
node.operator = operator;
node.operand = parenthesizerRules().parenthesizeOperandOfPrefixUnary(operand);
node.transformFlags |= propagateChildFlags(node.operand);
// Only set this flag for non-generated identifiers and non-"local" names. See the
// comment in `visitPreOrPostfixUnaryExpression` in module.ts
if ((operator === SyntaxKind.PlusPlusToken || operator === SyntaxKind.MinusMinusToken) &&
isIdentifier(node.operand) &&
!isGeneratedIdentifier(node.operand) &&
!isLocalName(node.operand)) {
node.transformFlags |= TransformFlags.ContainsUpdateExpressionForIdentifier;
}
return node;
}
@@ -2739,7 +2747,14 @@ namespace ts {
const node = createBaseExpression<PostfixUnaryExpression>(SyntaxKind.PostfixUnaryExpression);
node.operator = operator;
node.operand = parenthesizerRules().parenthesizeOperandOfPostfixUnary(operand);
node.transformFlags = propagateChildFlags(node.operand);
node.transformFlags |= propagateChildFlags(node.operand);
// Only set this flag for non-generated identifiers and non-"local" names. See the
// comment in `visitPreOrPostfixUnaryExpression` in module.ts
if (isIdentifier(node.operand) &&
!isGeneratedIdentifier(node.operand) &&
!isLocalName(node.operand)) {
node.transformFlags |= TransformFlags.ContainsUpdateExpressionForIdentifier;
}
return node;
}

View File

@@ -37,8 +37,6 @@ namespace ts {
context.enableSubstitution(SyntaxKind.TaggedTemplateExpression); // Substitute calls to imported/exported symbols to avoid incorrect `this`.
context.enableSubstitution(SyntaxKind.Identifier); // Substitutes expression identifiers with imported/exported symbols.
context.enableSubstitution(SyntaxKind.BinaryExpression); // Substitutes assignments to 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.
context.enableEmitNotification(SyntaxKind.SourceFile); // Restore state when substituting nodes in a file.
@@ -47,7 +45,7 @@ namespace ts {
let currentSourceFile: SourceFile; // The current file.
let currentModuleInfo: ExternalModuleInfo; // The ExternalModuleInfo for the current file.
let noSubstitution: boolean[]; // Set of nodes for which substitution rules should be ignored.
const noSubstitution: boolean[] = []; // Set of nodes for which substitution rules should be ignored.
let needUMDDynamicImportHelper: boolean;
return chainBundle(context, transformSourceFile);
@@ -96,7 +94,7 @@ namespace ts {
const statements: Statement[] = [];
const ensureUseStrict = getStrictOptionValue(compilerOptions, "alwaysStrict") || (!compilerOptions.noImplicitUseStrict && isExternalModule(currentSourceFile));
const statementOffset = factory.copyPrologue(node.statements, statements, ensureUseStrict && !isJsonSourceFile(node), sourceElementVisitor);
const statementOffset = factory.copyPrologue(node.statements, statements, ensureUseStrict && !isJsonSourceFile(node), topLevelVisitor);
if (shouldEmitUnderscoreUnderscoreESModule()) {
append(statements, createUnderscoreUnderscoreESModule());
@@ -117,8 +115,8 @@ namespace ts {
}
}
append(statements, visitNode(currentModuleInfo.externalHelpersImportDeclaration, sourceElementVisitor, isStatement));
addRange(statements, visitNodes(node.statements, sourceElementVisitor, isStatement, statementOffset));
append(statements, visitNode(currentModuleInfo.externalHelpersImportDeclaration, topLevelVisitor, isStatement));
addRange(statements, visitNodes(node.statements, topLevelVisitor, isStatement, statementOffset));
addExportEqualsIfNeeded(statements, /*emitAsReturn*/ false);
insertStatementsAfterStandardPrologue(statements, endLexicalEnvironment());
@@ -430,7 +428,7 @@ namespace ts {
startLexicalEnvironment();
const statements: Statement[] = [];
const statementOffset = factory.copyPrologue(node.statements, statements, /*ensureUseStrict*/ !compilerOptions.noImplicitUseStrict, sourceElementVisitor);
const statementOffset = factory.copyPrologue(node.statements, statements, /*ensureUseStrict*/ !compilerOptions.noImplicitUseStrict, topLevelVisitor);
if (shouldEmitUnderscoreUnderscoreESModule()) {
append(statements, createUnderscoreUnderscoreESModule());
@@ -440,11 +438,11 @@ namespace ts {
}
// Visit each statement of the module body.
append(statements, visitNode(currentModuleInfo.externalHelpersImportDeclaration, sourceElementVisitor, isStatement));
append(statements, visitNode(currentModuleInfo.externalHelpersImportDeclaration, topLevelVisitor, isStatement));
if (moduleKind === ModuleKind.AMD) {
addRange(statements, mapDefined(currentModuleInfo.externalImports, getAMDImportExpressionForImport));
}
addRange(statements, visitNodes(node.statements, sourceElementVisitor, isStatement, statementOffset));
addRange(statements, visitNodes(node.statements, topLevelVisitor, isStatement, statementOffset));
// Append the 'export =' statement if provided.
addExportEqualsIfNeeded(statements, /*emitAsReturn*/ true);
@@ -471,7 +469,7 @@ namespace ts {
*/
function addExportEqualsIfNeeded(statements: Statement[], emitAsReturn: boolean) {
if (currentModuleInfo.exportEquals) {
const expressionResult = visitNode(currentModuleInfo.exportEquals.expression, moduleExpressionElementVisitor);
const expressionResult = visitNode(currentModuleInfo.exportEquals.expression, visitor);
if (expressionResult) {
if (emitAsReturn) {
const statement = factory.createReturnStatement(expressionResult);
@@ -507,7 +505,7 @@ namespace ts {
*
* @param node The node to visit.
*/
function sourceElementVisitor(node: Node): VisitResult<Node> {
function topLevelVisitor(node: Node): VisitResult<Node> {
switch (node.kind) {
case SyntaxKind.ImportDeclaration:
return visitImportDeclaration(node as ImportDeclaration);
@@ -537,26 +535,50 @@ namespace ts {
return visitEndOfDeclarationMarker(node as EndOfDeclarationMarker);
default:
return visitEachChild(node, moduleExpressionElementVisitor, context);
return visitor(node);
}
}
function moduleExpressionElementVisitor(node: Expression): VisitResult<Expression> {
// This visitor does not need to descend into the tree if there is no dynamic import or destructuring assignment,
function visitorWorker(node: Node, valueIsDiscarded: boolean): VisitResult<Node> {
// This visitor does not need to descend into the tree if there is no dynamic import, destructuring assignment, or update expression
// as export/import statements are only transformed at the top level of a file.
if (!(node.transformFlags & TransformFlags.ContainsDynamicImport) && !(node.transformFlags & TransformFlags.ContainsDestructuringAssignment)) {
if (!(node.transformFlags & (TransformFlags.ContainsDynamicImport | TransformFlags.ContainsDestructuringAssignment | TransformFlags.ContainsUpdateExpressionForIdentifier))) {
return node;
}
if (isImportCall(node)) {
return visitImportCallExpression(node);
}
else if (isDestructuringAssignment(node)) {
return visitDestructuringAssignment(node);
}
else {
return visitEachChild(node, moduleExpressionElementVisitor, context);
switch (node.kind) {
case SyntaxKind.ForStatement:
return visitForStatement(node as ForStatement);
case SyntaxKind.ExpressionStatement:
return visitExpressionStatement(node as ExpressionStatement);
case SyntaxKind.ParenthesizedExpression:
return visitParenthesizedExpression(node as ParenthesizedExpression, valueIsDiscarded);
case SyntaxKind.PartiallyEmittedExpression:
return visitPartiallyEmittedExpression(node as PartiallyEmittedExpression, valueIsDiscarded);
case SyntaxKind.CallExpression:
if (isImportCall(node)) {
return visitImportCallExpression(node);
}
break;
case SyntaxKind.BinaryExpression:
if (isDestructuringAssignment(node)) {
return visitDestructuringAssignment(node, valueIsDiscarded);
}
break;
case SyntaxKind.PrefixUnaryExpression:
case SyntaxKind.PostfixUnaryExpression:
return visitPreOrPostfixUnaryExpression(node as PrefixUnaryExpression | PostfixUnaryExpression, valueIsDiscarded);
}
return visitEachChild(node, visitor, context);
}
function visitor(node: Node): VisitResult<Node> {
return visitorWorker(node, /*valueIsDiscarded*/ false);
}
function discardedValueVisitor(node: Node): VisitResult<Node> {
return visitorWorker(node, /*valueIsDiscarded*/ true);
}
function destructuringNeedsFlattening(node: Expression): boolean {
@@ -604,16 +626,91 @@ namespace ts {
return false;
}
function visitDestructuringAssignment(node: DestructuringAssignment): Expression {
function visitDestructuringAssignment(node: DestructuringAssignment, valueIsDiscarded: boolean): Expression {
if (destructuringNeedsFlattening(node.left)) {
return flattenDestructuringAssignment(node, moduleExpressionElementVisitor, context, FlattenLevel.All, /*needsValue*/ false, createAllExportExpressions);
return flattenDestructuringAssignment(node, visitor, context, FlattenLevel.All, !valueIsDiscarded, createAllExportExpressions);
}
return visitEachChild(node, moduleExpressionElementVisitor, context);
return visitEachChild(node, visitor, context);
}
function visitForStatement(node: ForStatement) {
return factory.updateForStatement(
node,
visitNode(node.initializer, discardedValueVisitor, isForInitializer),
visitNode(node.condition, visitor, isExpression),
visitNode(node.incrementor, discardedValueVisitor, isExpression),
visitIterationBody(node.statement, visitor, context)
);
}
function visitExpressionStatement(node: ExpressionStatement) {
return factory.updateExpressionStatement(
node,
visitNode(node.expression, discardedValueVisitor, isExpression)
);
}
function visitParenthesizedExpression(node: ParenthesizedExpression, valueIsDiscarded: boolean) {
return factory.updateParenthesizedExpression(node, visitNode(node.expression, valueIsDiscarded ? discardedValueVisitor : visitor, isExpression));
}
function visitPartiallyEmittedExpression(node: PartiallyEmittedExpression, valueIsDiscarded: boolean) {
return factory.updatePartiallyEmittedExpression(node, visitNode(node.expression, valueIsDiscarded ? discardedValueVisitor : visitor, isExpression));
}
function visitPreOrPostfixUnaryExpression(node: PrefixUnaryExpression | PostfixUnaryExpression, valueIsDiscarded: boolean) {
// When we see a prefix or postfix increment expression whose operand is an exported
// symbol, we should ensure all exports of that symbol are updated with the correct
// value.
//
// - We do not transform generated identifiers for any reason.
// - We do not transform identifiers tagged with the LocalName flag.
// - We do not transform identifiers that were originally the name of an enum or
// namespace due to how they are transformed in TypeScript.
// - We only transform identifiers that are exported at the top level.
if ((node.operator === SyntaxKind.PlusPlusToken || node.operator === SyntaxKind.MinusMinusToken)
&& isIdentifier(node.operand)
&& !isGeneratedIdentifier(node.operand)
&& !isLocalName(node.operand)
&& !isDeclarationNameOfEnumOrNamespace(node.operand)) {
const exportedNames = getExports(node.operand);
if (exportedNames) {
let temp: Identifier | undefined;
let expression: Expression = visitNode(node.operand, visitor, isExpression);
if (isPrefixUnaryExpression(node)) {
expression = factory.updatePrefixUnaryExpression(node, expression);
}
else {
expression = factory.updatePostfixUnaryExpression(node, expression);
if (!valueIsDiscarded) {
temp = factory.createTempVariable(hoistVariableDeclaration);
expression = factory.createAssignment(temp, expression);
setTextRange(expression, node);
}
expression = factory.createComma(expression, factory.cloneNode(node.operand));
setTextRange(expression, node);
}
for (const exportName of exportedNames) {
noSubstitution[getNodeId(expression)] = true;
expression = createExportExpression(exportName, expression);
setTextRange(expression, node);
}
if (temp) {
noSubstitution[getNodeId(expression)] = true;
expression = factory.createComma(expression, temp);
setTextRange(expression, node);
}
return expression;
}
}
return visitEachChild(node, visitor, context);
}
function visitImportCallExpression(node: ImportCall): Expression {
const externalModuleName = getExternalModuleNameLiteral(factory, node, currentSourceFile, host, resolver, compilerOptions);
const firstArgument = visitNode(firstOrUndefined(node.arguments), moduleExpressionElementVisitor);
const firstArgument = visitNode(firstOrUndefined(node.arguments), visitor);
// Only use the external module name if it differs from the first argument. This allows us to preserve the quote style of the argument on output.
const argument = externalModuleName && (!firstArgument || !isStringLiteral(firstArgument) || firstArgument.text !== externalModuleName.text) ? externalModuleName : firstArgument;
const containsLexicalThis = !!(node.transformFlags & TransformFlags.ContainsLexicalThis);
@@ -1110,10 +1207,10 @@ namespace ts {
if (original && hasAssociatedEndOfDeclarationMarker(original)) {
// Defer exports until we encounter an EndOfDeclarationMarker node
const id = getOriginalNodeId(node);
deferredExports[id] = appendExportStatement(deferredExports[id], factory.createIdentifier("default"), visitNode(node.expression, moduleExpressionElementVisitor), /*location*/ node, /*allowComments*/ true);
deferredExports[id] = appendExportStatement(deferredExports[id], factory.createIdentifier("default"), visitNode(node.expression, visitor), /*location*/ node, /*allowComments*/ true);
}
else {
statements = appendExportStatement(statements, factory.createIdentifier("default"), visitNode(node.expression, moduleExpressionElementVisitor), /*location*/ node, /*allowComments*/ true);
statements = appendExportStatement(statements, factory.createIdentifier("default"), visitNode(node.expression, visitor), /*location*/ node, /*allowComments*/ true);
}
return singleOrMany(statements);
@@ -1136,9 +1233,9 @@ namespace ts {
node.asteriskToken,
factory.getDeclarationName(node, /*allowComments*/ true, /*allowSourceMaps*/ true),
/*typeParameters*/ undefined,
visitNodes(node.parameters, moduleExpressionElementVisitor),
visitNodes(node.parameters, visitor),
/*type*/ undefined,
visitEachChild(node.body, moduleExpressionElementVisitor, context)
visitEachChild(node.body, visitor, context)
),
/*location*/ node
),
@@ -1147,7 +1244,7 @@ namespace ts {
);
}
else {
statements = append(statements, visitEachChild(node, moduleExpressionElementVisitor, context));
statements = append(statements, visitEachChild(node, visitor, context));
}
if (hasAssociatedEndOfDeclarationMarker(node)) {
@@ -1178,8 +1275,8 @@ namespace ts {
visitNodes(node.modifiers, modifierVisitor, isModifier),
factory.getDeclarationName(node, /*allowComments*/ true, /*allowSourceMaps*/ true),
/*typeParameters*/ undefined,
visitNodes(node.heritageClauses, moduleExpressionElementVisitor),
visitNodes(node.members, moduleExpressionElementVisitor)
visitNodes(node.heritageClauses, visitor),
visitNodes(node.members, visitor)
),
node
),
@@ -1188,7 +1285,7 @@ namespace ts {
);
}
else {
statements = append(statements, visitEachChild(node, moduleExpressionElementVisitor, context));
statements = append(statements, visitEachChild(node, visitor, context));
}
if (hasAssociatedEndOfDeclarationMarker(node)) {
@@ -1242,7 +1339,7 @@ namespace ts {
variable.name,
variable.exclamationToken,
variable.type,
visitNode(variable.initializer, moduleExpressionElementVisitor)
visitNode(variable.initializer, visitor)
);
variables = append(variables, updatedVariable);
@@ -1268,7 +1365,7 @@ namespace ts {
}
}
else {
statements = append(statements, visitEachChild(node, moduleExpressionElementVisitor, context));
statements = append(statements, visitEachChild(node, visitor, context));
}
if (hasAssociatedEndOfDeclarationMarker(node)) {
@@ -1307,7 +1404,7 @@ namespace ts {
function transformInitializedVariable(node: InitializedVariableDeclaration): Expression {
if (isBindingPattern(node.name)) {
return flattenDestructuringAssignment(
visitNode(node, moduleExpressionElementVisitor),
visitNode(node, visitor),
/*visitor*/ undefined,
context,
FlattenLevel.All,
@@ -1324,7 +1421,7 @@ namespace ts {
),
/*location*/ node.name
),
node.initializer ? visitNode(node.initializer, moduleExpressionElementVisitor) : factory.createVoidZero()
node.initializer ? visitNode(node.initializer, visitor) : factory.createVoidZero()
);
}
}
@@ -1674,13 +1771,11 @@ namespace ts {
if (node.kind === SyntaxKind.SourceFile) {
currentSourceFile = node as SourceFile;
currentModuleInfo = moduleInfoMap[getOriginalNodeId(currentSourceFile)];
noSubstitution = [];
previousOnEmitNode(hint, node, emitCallback);
currentSourceFile = undefined!;
currentModuleInfo = undefined!;
noSubstitution = undefined!;
}
else {
previousOnEmitNode(hint, node, emitCallback);
@@ -1749,9 +1844,6 @@ namespace ts {
return substituteTaggedTemplateExpression(node as TaggedTemplateExpression);
case SyntaxKind.BinaryExpression:
return substituteBinaryExpression(node as BinaryExpression);
case SyntaxKind.PostfixUnaryExpression:
case SyntaxKind.PrefixUnaryExpression:
return substituteUnaryExpression(node as PrefixUnaryExpression | PostfixUnaryExpression);
}
return node;
@@ -1881,54 +1973,6 @@ namespace ts {
return node;
}
/**
* Substitution for a UnaryExpression that may contain an imported or exported symbol.
*
* @param node The node to substitute.
*/
function substituteUnaryExpression(node: PrefixUnaryExpression | PostfixUnaryExpression): Expression {
// When we see a prefix or postfix increment expression whose operand is an exported
// symbol, we should ensure all exports of that symbol are updated with the correct
// value.
//
// - We do not substitute generated identifiers for any reason.
// - We do not substitute identifiers tagged with the LocalName flag.
// - We do not substitute identifiers that were originally the name of an enum or
// namespace due to how they are transformed in TypeScript.
// - We only substitute identifiers that are exported at the top level.
if ((node.operator === SyntaxKind.PlusPlusToken || node.operator === SyntaxKind.MinusMinusToken)
&& isIdentifier(node.operand)
&& !isGeneratedIdentifier(node.operand)
&& !isLocalName(node.operand)
&& !isDeclarationNameOfEnumOrNamespace(node.operand)) {
const exportedNames = getExports(node.operand);
if (exportedNames) {
let expression: Expression = node.kind === SyntaxKind.PostfixUnaryExpression
? setTextRange(
factory.createPrefixUnaryExpression(
node.operator,
node.operand
),
/*location*/ node)
: node;
for (const exportName of exportedNames) {
// Mark the node to prevent triggering this rule again.
noSubstitution[getNodeId(expression)] = true;
expression = createExportExpression(exportName, expression);
}
if (node.kind === SyntaxKind.PostfixUnaryExpression) {
noSubstitution[getNodeId(expression)] = true;
expression = node.operator === SyntaxKind.PlusPlusToken
? factory.createSubtract(expression, factory.createNumericLiteral(1))
: factory.createAdd(expression, factory.createNumericLiteral(1));
}
return expression;
}
}
return node;
}
/**
* Gets the additional exports of a name.
*

View File

@@ -23,8 +23,6 @@ namespace ts {
context.enableSubstitution(SyntaxKind.Identifier); // Substitutes expression identifiers for imported symbols.
context.enableSubstitution(SyntaxKind.ShorthandPropertyAssignment); // Substitutes expression identifiers for imported symbols
context.enableSubstitution(SyntaxKind.BinaryExpression); // Substitutes assignments to exported symbols.
context.enableSubstitution(SyntaxKind.PrefixUnaryExpression); // Substitutes updates to exported symbols.
context.enableSubstitution(SyntaxKind.PostfixUnaryExpression); // Substitutes updates to exported symbols.
context.enableSubstitution(SyntaxKind.MetaProperty); // Substitutes 'import.meta'
context.enableEmitNotification(SyntaxKind.SourceFile); // Restore state when substituting nodes in a file.
@@ -225,7 +223,7 @@ namespace ts {
// Add any prologue directives.
const ensureUseStrict = getStrictOptionValue(compilerOptions, "alwaysStrict") || (!compilerOptions.noImplicitUseStrict && isExternalModule(currentSourceFile));
const statementOffset = factory.copyPrologue(node.statements, statements, ensureUseStrict, sourceElementVisitor);
const statementOffset = factory.copyPrologue(node.statements, statements, ensureUseStrict, topLevelVisitor);
// var __moduleName = context_1 && context_1.id;
statements.push(
@@ -246,14 +244,14 @@ namespace ts {
);
// Visit the synthetic external helpers import declaration if present
visitNode(moduleInfo.externalHelpersImportDeclaration, sourceElementVisitor, isStatement);
visitNode(moduleInfo.externalHelpersImportDeclaration, topLevelVisitor, isStatement);
// Visit the statements of the source file, emitting any transformations into
// the `executeStatements` array. We do this *before* we fill the `setters` array
// as we both emit transformations as well as aggregate some data used when creating
// setters. This allows us to reduce the number of times we need to loop through the
// statements of the source file.
const executeStatements = visitNodes(node.statements, sourceElementVisitor, isStatement, statementOffset);
const executeStatements = visitNodes(node.statements, topLevelVisitor, isStatement, statementOffset);
// Emit early exports for function declarations.
addRange(statements, hoistedStatements);
@@ -566,7 +564,7 @@ namespace ts {
*
* @param node The node to visit.
*/
function sourceElementVisitor(node: Node): VisitResult<Node> {
function topLevelVisitor(node: Node): VisitResult<Node> {
switch (node.kind) {
case SyntaxKind.ImportDeclaration:
return visitImportDeclaration(node as ImportDeclaration);
@@ -581,7 +579,7 @@ namespace ts {
return visitExportAssignment(node as ExportAssignment);
default:
return nestedElementVisitor(node);
return topLevelNestedVisitor(node);
}
}
@@ -647,7 +645,7 @@ namespace ts {
return undefined;
}
const expression = visitNode(node.expression, destructuringAndImportCallVisitor, isExpression);
const expression = visitNode(node.expression, visitor, isExpression);
const original = node.original;
if (original && hasAssociatedEndOfDeclarationMarker(original)) {
// Defer exports until we encounter an EndOfDeclarationMarker node
@@ -674,12 +672,12 @@ namespace ts {
node.asteriskToken,
factory.getDeclarationName(node, /*allowComments*/ true, /*allowSourceMaps*/ true),
/*typeParameters*/ undefined,
visitNodes(node.parameters, destructuringAndImportCallVisitor, isParameterDeclaration),
visitNodes(node.parameters, visitor, isParameterDeclaration),
/*type*/ undefined,
visitNode(node.body, destructuringAndImportCallVisitor, isBlock)));
visitNode(node.body, visitor, isBlock)));
}
else {
hoistedStatements = append(hoistedStatements, visitEachChild(node, destructuringAndImportCallVisitor, context));
hoistedStatements = append(hoistedStatements, visitEachChild(node, visitor, context));
}
if (hasAssociatedEndOfDeclarationMarker(node)) {
@@ -714,12 +712,12 @@ namespace ts {
name,
setTextRange(
factory.createClassExpression(
visitNodes(node.decorators, destructuringAndImportCallVisitor, isDecorator),
visitNodes(node.decorators, visitor, isDecorator),
/*modifiers*/ undefined,
node.name,
/*typeParameters*/ undefined,
visitNodes(node.heritageClauses, destructuringAndImportCallVisitor, isHeritageClause),
visitNodes(node.members, destructuringAndImportCallVisitor, isClassElement)
visitNodes(node.heritageClauses, visitor, isHeritageClause),
visitNodes(node.members, visitor, isClassElement)
),
node
)
@@ -749,7 +747,7 @@ namespace ts {
*/
function visitVariableStatement(node: VariableStatement): VisitResult<Statement> {
if (!shouldHoistVariableDeclarationList(node.declarationList)) {
return visitNode(node, destructuringAndImportCallVisitor, isStatement);
return visitNode(node, visitor, isStatement);
}
let expressions: Expression[] | undefined;
@@ -822,13 +820,13 @@ namespace ts {
return isBindingPattern(node.name)
? flattenDestructuringAssignment(
node,
destructuringAndImportCallVisitor,
visitor,
context,
FlattenLevel.All,
/*needsValue*/ false,
createAssignment
)
: node.initializer ? createAssignment(node.name, visitNode(node.initializer, destructuringAndImportCallVisitor, isExpression)) : node.name;
: node.initializer ? createAssignment(node.name, visitNode(node.initializer, visitor, isExpression)) : node.name;
}
/**
@@ -1153,7 +1151,7 @@ namespace ts {
*
* @param node The node to visit.
*/
function nestedElementVisitor(node: Node): VisitResult<Node> {
function topLevelNestedVisitor(node: Node): VisitResult<Node> {
switch (node.kind) {
case SyntaxKind.VariableStatement:
return visitVariableStatement(node as VariableStatement);
@@ -1165,7 +1163,7 @@ namespace ts {
return visitClassDeclaration(node as ClassDeclaration);
case SyntaxKind.ForStatement:
return visitForStatement(node as ForStatement);
return visitForStatement(node as ForStatement, /*isTopLevel*/ true);
case SyntaxKind.ForInStatement:
return visitForInStatement(node as ForInStatement);
@@ -1213,7 +1211,7 @@ namespace ts {
return visitEndOfDeclarationMarker(node as EndOfDeclarationMarker);
default:
return destructuringAndImportCallVisitor(node);
return visitor(node);
}
}
@@ -1222,16 +1220,16 @@ namespace ts {
*
* @param node The node to visit.
*/
function visitForStatement(node: ForStatement): VisitResult<Statement> {
function visitForStatement(node: ForStatement, isTopLevel: boolean): VisitResult<Statement> {
const savedEnclosingBlockScopedContainer = enclosingBlockScopedContainer;
enclosingBlockScopedContainer = node;
node = factory.updateForStatement(
node,
node.initializer && visitForInitializer(node.initializer),
visitNode(node.condition, destructuringAndImportCallVisitor, isExpression),
visitNode(node.incrementor, destructuringAndImportCallVisitor, isExpression),
visitIterationBody(node.statement, nestedElementVisitor, context)
visitNode(node.initializer, isTopLevel ? visitForInitializer : discardedValueVisitor, isForInitializer),
visitNode(node.condition, visitor, isExpression),
visitNode(node.incrementor, discardedValueVisitor, isExpression),
visitIterationBody(node.statement, isTopLevel ? topLevelNestedVisitor : visitor, context)
);
enclosingBlockScopedContainer = savedEnclosingBlockScopedContainer;
@@ -1250,8 +1248,8 @@ namespace ts {
node = factory.updateForInStatement(
node,
visitForInitializer(node.initializer),
visitNode(node.expression, destructuringAndImportCallVisitor, isExpression),
visitIterationBody(node.statement, nestedElementVisitor, context)
visitNode(node.expression, visitor, isExpression),
visitIterationBody(node.statement, topLevelNestedVisitor, context)
);
enclosingBlockScopedContainer = savedEnclosingBlockScopedContainer;
@@ -1271,8 +1269,8 @@ namespace ts {
node,
node.awaitModifier,
visitForInitializer(node.initializer),
visitNode(node.expression, destructuringAndImportCallVisitor, isExpression),
visitIterationBody(node.statement, nestedElementVisitor, context)
visitNode(node.expression, visitor, isExpression),
visitIterationBody(node.statement, topLevelNestedVisitor, context)
);
enclosingBlockScopedContainer = savedEnclosingBlockScopedContainer;
@@ -1308,7 +1306,7 @@ namespace ts {
return expressions ? factory.inlineExpressions(expressions) : factory.createOmittedExpression();
}
else {
return visitEachChild(node, nestedElementVisitor, context);
return visitNode(node, discardedValueVisitor, isExpression);
}
}
@@ -1320,8 +1318,8 @@ namespace ts {
function visitDoStatement(node: DoStatement): VisitResult<Statement> {
return factory.updateDoStatement(
node,
visitIterationBody(node.statement, nestedElementVisitor, context),
visitNode(node.expression, destructuringAndImportCallVisitor, isExpression)
visitIterationBody(node.statement, topLevelNestedVisitor, context),
visitNode(node.expression, visitor, isExpression)
);
}
@@ -1333,8 +1331,8 @@ namespace ts {
function visitWhileStatement(node: WhileStatement): VisitResult<Statement> {
return factory.updateWhileStatement(
node,
visitNode(node.expression, destructuringAndImportCallVisitor, isExpression),
visitIterationBody(node.statement, nestedElementVisitor, context)
visitNode(node.expression, visitor, isExpression),
visitIterationBody(node.statement, topLevelNestedVisitor, context)
);
}
@@ -1347,7 +1345,7 @@ namespace ts {
return factory.updateLabeledStatement(
node,
node.label,
visitNode(node.statement, nestedElementVisitor, isStatement, factory.liftToBlock)
visitNode(node.statement, topLevelNestedVisitor, isStatement, factory.liftToBlock)
);
}
@@ -1359,8 +1357,8 @@ namespace ts {
function visitWithStatement(node: WithStatement): VisitResult<Statement> {
return factory.updateWithStatement(
node,
visitNode(node.expression, destructuringAndImportCallVisitor, isExpression),
visitNode(node.statement, nestedElementVisitor, isStatement, factory.liftToBlock)
visitNode(node.expression, visitor, isExpression),
visitNode(node.statement, topLevelNestedVisitor, isStatement, factory.liftToBlock)
);
}
@@ -1372,8 +1370,8 @@ namespace ts {
function visitSwitchStatement(node: SwitchStatement): VisitResult<Statement> {
return factory.updateSwitchStatement(
node,
visitNode(node.expression, destructuringAndImportCallVisitor, isExpression),
visitNode(node.caseBlock, nestedElementVisitor, isCaseBlock)
visitNode(node.expression, visitor, isExpression),
visitNode(node.caseBlock, topLevelNestedVisitor, isCaseBlock)
);
}
@@ -1388,7 +1386,7 @@ namespace ts {
node = factory.updateCaseBlock(
node,
visitNodes(node.clauses, nestedElementVisitor, isCaseOrDefaultClause)
visitNodes(node.clauses, topLevelNestedVisitor, isCaseOrDefaultClause)
);
enclosingBlockScopedContainer = savedEnclosingBlockScopedContainer;
@@ -1403,8 +1401,8 @@ namespace ts {
function visitCaseClause(node: CaseClause): VisitResult<CaseOrDefaultClause> {
return factory.updateCaseClause(
node,
visitNode(node.expression, destructuringAndImportCallVisitor, isExpression),
visitNodes(node.statements, nestedElementVisitor, isStatement)
visitNode(node.expression, visitor, isExpression),
visitNodes(node.statements, topLevelNestedVisitor, isStatement)
);
}
@@ -1414,7 +1412,7 @@ namespace ts {
* @param node The node to visit.
*/
function visitDefaultClause(node: DefaultClause): VisitResult<CaseOrDefaultClause> {
return visitEachChild(node, nestedElementVisitor, context);
return visitEachChild(node, topLevelNestedVisitor, context);
}
/**
@@ -1423,7 +1421,7 @@ namespace ts {
* @param node The node to visit.
*/
function visitTryStatement(node: TryStatement): VisitResult<Statement> {
return visitEachChild(node, nestedElementVisitor, context);
return visitEachChild(node, topLevelNestedVisitor, context);
}
/**
@@ -1438,7 +1436,7 @@ namespace ts {
node = factory.updateCatchClause(
node,
node.variableDeclaration,
visitNode(node.block, nestedElementVisitor, isBlock)
visitNode(node.block, topLevelNestedVisitor, isBlock)
);
enclosingBlockScopedContainer = savedEnclosingBlockScopedContainer;
@@ -1454,7 +1452,7 @@ namespace ts {
const savedEnclosingBlockScopedContainer = enclosingBlockScopedContainer;
enclosingBlockScopedContainer = node;
node = visitEachChild(node, nestedElementVisitor, context);
node = visitEachChild(node, topLevelNestedVisitor, context);
enclosingBlockScopedContainer = savedEnclosingBlockScopedContainer;
return node;
@@ -1469,19 +1467,59 @@ namespace ts {
*
* @param node The node to visit.
*/
function destructuringAndImportCallVisitor(node: Node): VisitResult<Node> {
if (isDestructuringAssignment(node)) {
return visitDestructuringAssignment(node);
}
else if (isImportCall(node)) {
return visitImportCallExpression(node);
}
else if ((node.transformFlags & TransformFlags.ContainsDestructuringAssignment) || (node.transformFlags & TransformFlags.ContainsDynamicImport)) {
return visitEachChild(node, destructuringAndImportCallVisitor, context);
}
else {
function visitorWorker(node: Node, valueIsDiscarded: boolean): VisitResult<Node> {
if (!(node.transformFlags & (TransformFlags.ContainsDestructuringAssignment | TransformFlags.ContainsDynamicImport | TransformFlags.ContainsUpdateExpressionForIdentifier))) {
return node;
}
switch (node.kind) {
case SyntaxKind.ForStatement:
return visitForStatement(node as ForStatement, /*isTopLevel*/ false);
case SyntaxKind.ExpressionStatement:
return visitExpressionStatement(node as ExpressionStatement);
case SyntaxKind.ParenthesizedExpression:
return visitParenthesizedExpression(node as ParenthesizedExpression, valueIsDiscarded);
case SyntaxKind.PartiallyEmittedExpression:
return visitPartiallyEmittedExpression(node as PartiallyEmittedExpression, valueIsDiscarded);
case SyntaxKind.BinaryExpression:
if (isDestructuringAssignment(node)) {
return visitDestructuringAssignment(node, valueIsDiscarded);
}
break;
case SyntaxKind.CallExpression:
if (isImportCall(node)) {
return visitImportCallExpression(node);
}
break;
case SyntaxKind.PrefixUnaryExpression:
case SyntaxKind.PostfixUnaryExpression:
return visitPrefixOrPostfixUnaryExpression(node as PrefixUnaryExpression | PostfixUnaryExpression, valueIsDiscarded);
}
return visitEachChild(node, visitor, context);
}
/**
* Visit nodes to flatten destructuring assignments to exported symbols.
*
* @param node The node to visit.
*/
function visitor(node: Node): VisitResult<Node> {
return visitorWorker(node, /*valueIsDiscarded*/ false);
}
function discardedValueVisitor(node: Node): VisitResult<Node> {
return visitorWorker(node, /*valueIsDiscarded*/ true);
}
function visitExpressionStatement(node: ExpressionStatement) {
return factory.updateExpressionStatement(node, visitNode(node.expression, discardedValueVisitor, isExpression));
}
function visitParenthesizedExpression(node: ParenthesizedExpression, valueIsDiscarded: boolean) {
return factory.updateParenthesizedExpression(node, visitNode(node.expression, valueIsDiscarded ? discardedValueVisitor : visitor, isExpression));
}
function visitPartiallyEmittedExpression(node: PartiallyEmittedExpression, valueIsDiscarded: boolean) {
return factory.updatePartiallyEmittedExpression(node, visitNode(node.expression, valueIsDiscarded ? discardedValueVisitor : visitor, isExpression));
}
function visitImportCallExpression(node: ImportCall): Expression {
@@ -1496,7 +1534,7 @@ namespace ts {
// };
// });
const externalModuleName = getExternalModuleNameLiteral(factory, node, currentSourceFile, host, resolver, compilerOptions);
const firstArgument = visitNode(firstOrUndefined(node.arguments), destructuringAndImportCallVisitor);
const firstArgument = visitNode(firstOrUndefined(node.arguments), visitor);
// Only use the external module name if it differs from the first argument. This allows us to preserve the quote style of the argument on output.
const argument = externalModuleName && (!firstArgument || !isStringLiteral(firstArgument) || firstArgument.text !== externalModuleName.text) ? externalModuleName : firstArgument;
return factory.createCallExpression(
@@ -1514,18 +1552,18 @@ namespace ts {
*
* @param node The node to visit.
*/
function visitDestructuringAssignment(node: DestructuringAssignment): VisitResult<Expression> {
function visitDestructuringAssignment(node: DestructuringAssignment, valueIsDiscarded: boolean): VisitResult<Expression> {
if (hasExportedReferenceInDestructuringTarget(node.left)) {
return flattenDestructuringAssignment(
node,
destructuringAndImportCallVisitor,
visitor,
context,
FlattenLevel.All,
/*needsValue*/ true
!valueIsDiscarded
);
}
return visitEachChild(node, destructuringAndImportCallVisitor, context);
return visitEachChild(node, visitor, context);
}
/**
@@ -1561,6 +1599,54 @@ namespace ts {
}
}
function visitPrefixOrPostfixUnaryExpression(node: PrefixUnaryExpression | PostfixUnaryExpression, valueIsDiscarded: boolean) {
// When we see a prefix or postfix increment expression whose operand is an exported
// symbol, we should ensure all exports of that symbol are updated with the correct
// value.
//
// - We do not transform generated identifiers for any reason.
// - We do not transform identifiers tagged with the LocalName flag.
// - We do not transform identifiers that were originally the name of an enum or
// namespace due to how they are transformed in TypeScript.
// - We only transform identifiers that are exported at the top level.
if ((node.operator === SyntaxKind.PlusPlusToken || node.operator === SyntaxKind.MinusMinusToken)
&& isIdentifier(node.operand)
&& !isGeneratedIdentifier(node.operand)
&& !isLocalName(node.operand)
&& !isDeclarationNameOfEnumOrNamespace(node.operand)) {
const exportedNames = getExports(node.operand);
if (exportedNames) {
let temp: Identifier | undefined;
let expression: Expression = visitNode(node.operand, visitor, isExpression);
if (isPrefixUnaryExpression(node)) {
expression = factory.updatePrefixUnaryExpression(node, expression);
}
else {
expression = factory.updatePostfixUnaryExpression(node, expression);
if (!valueIsDiscarded) {
temp = factory.createTempVariable(hoistVariableDeclaration);
expression = factory.createAssignment(temp, expression);
setTextRange(expression, node);
}
expression = factory.createComma(expression, factory.cloneNode(node.operand));
setTextRange(expression, node);
}
for (const exportName of exportedNames) {
expression = createExportExpression(exportName, preventSubstitution(expression));
}
if (temp) {
expression = factory.createComma(expression, temp);
setTextRange(expression, node);
}
return expression;
}
}
return visitEachChild(node, visitor, context);
}
//
// Modifier Visitors
//
@@ -1705,9 +1791,6 @@ namespace ts {
return substituteExpressionIdentifier(node as Identifier);
case SyntaxKind.BinaryExpression:
return substituteBinaryExpression(node as BinaryExpression);
case SyntaxKind.PrefixUnaryExpression:
case SyntaxKind.PostfixUnaryExpression:
return substituteUnaryExpression(node as PrefixUnaryExpression | PostfixUnaryExpression);
case SyntaxKind.MetaProperty:
return substituteMetaProperty(node as MetaProperty);
}
@@ -1797,55 +1880,6 @@ namespace ts {
return node;
}
/**
* Substitution for a UnaryExpression that may contain an imported or exported symbol.
*
* @param node The node to substitute.
*/
function substituteUnaryExpression(node: PrefixUnaryExpression | PostfixUnaryExpression): Expression {
// When we see a prefix or postfix increment expression whose operand is an exported
// symbol, we should ensure all exports of that symbol are updated with the correct
// value.
//
// - We do not substitute generated identifiers for any reason.
// - We do not substitute identifiers tagged with the LocalName flag.
// - We do not substitute identifiers that were originally the name of an enum or
// namespace due to how they are transformed in TypeScript.
// - We only substitute identifiers that are exported at the top level.
if ((node.operator === SyntaxKind.PlusPlusToken || node.operator === SyntaxKind.MinusMinusToken)
&& isIdentifier(node.operand)
&& !isGeneratedIdentifier(node.operand)
&& !isLocalName(node.operand)
&& !isDeclarationNameOfEnumOrNamespace(node.operand)) {
const exportedNames = getExports(node.operand);
if (exportedNames) {
let expression: Expression = node.kind === SyntaxKind.PostfixUnaryExpression
? setTextRange(
factory.createPrefixUnaryExpression(
node.operator,
node.operand
),
node
)
: node;
for (const exportName of exportedNames) {
expression = createExportExpression(exportName, preventSubstitution(expression));
}
if (node.kind === SyntaxKind.PostfixUnaryExpression) {
expression = node.operator === SyntaxKind.PlusPlusToken
? factory.createSubtract(preventSubstitution(expression), factory.createNumericLiteral(1))
: factory.createAdd(preventSubstitution(expression), factory.createNumericLiteral(1));
}
return expression;
}
}
return node;
}
function substituteMetaProperty(node: MetaProperty) {
if (isImportMeta(node)) {
return factory.createPropertyAccessExpression(contextObject, factory.createIdentifier("meta"));

View File

@@ -6640,6 +6640,7 @@ namespace ts {
ContainsClassFields = 1 << 23,
ContainsPossibleTopLevelAwait = 1 << 24,
ContainsLexicalSuper = 1 << 25,
ContainsUpdateExpressionForIdentifier = 1 << 26,
// Please leave this as 1 << 29.
// It is the maximum bit we can set before we outgrow the size of a v8 small integer (SMI) on an x86 system.
// It is a good reminder of how much room we have left

View File

@@ -36,98 +36,424 @@ namespace evaluator {
const output = result.getOutput(source.main, "js")!;
assert.isDefined(output);
globals = { Symbol: FakeSymbol, ...globals };
return createLoader(fs, globals)(output.file);
}
function createLoader(fs: vfs.FileSystem, globals: Record<string, any>) {
interface Module {
exports: any;
}
const moduleCache = new ts.Map<string, Module>();
return load;
function evaluate(text: string, file: string, module: Module) {
const globalNames: string[] = [];
const globalArgs: any[] = [];
for (const name in globals) {
if (ts.hasProperty(globals, name)) {
globalNames.push(name);
globalArgs.push(globals[name]);
}
}
const base = vpath.dirname(file);
const localRequire = (id: string) => requireModule(id, base);
const evaluateText = `(function (module, exports, require, __dirname, __filename, ${globalNames.join(", ")}) { ${text} })`;
// eslint-disable-next-line no-eval
const evaluateThunk = (void 0, eval)(evaluateText) as (module: any, exports: any, require: (id: string) => any, dirname: string, filename: string, ...globalArgs: any[]) => void;
evaluateThunk.call(globals, module, module.exports, localRequire, vpath.dirname(file), file, FakeSymbol, ...globalArgs);
}
function loadModule(file: string): Module {
if (!ts.isExternalModuleNameRelative(file)) throw new Error(`Module '${file}' could not be found.`);
let module = moduleCache.get(file);
if (module) return module;
moduleCache.set(file, module = { exports: {} });
try {
const sourceText = fs.readFileSync(file, "utf8");
evaluate(sourceText, file, module);
return module;
}
catch (e) {
moduleCache.delete(file);
throw e;
}
}
function isFile(file: string) {
return fs.existsSync(file) && fs.statSync(file).isFile();
}
function loadAsFile(file: string): Module | undefined {
if (isFile(file)) return loadModule(file);
if (isFile(file + ".js")) return loadModule(file + ".js");
return undefined;
}
function loadIndex(dir: string): Module | undefined {
const indexFile = vpath.resolve(dir, "index.js");
if (isFile(indexFile)) return loadModule(indexFile);
return undefined;
}
function loadAsDirectory(dir: string): Module | undefined {
const packageFile = vpath.resolve(dir, "package.json");
if (isFile(packageFile)) {
const text = fs.readFileSync(packageFile, "utf8");
const json = JSON.parse(text);
if (json.main) {
const main = vpath.resolve(dir, json.main);
const result = loadAsFile(main) || loadIndex(main);
if (result === undefined) throw new Error("Module not found");
}
}
return loadIndex(dir);
}
function requireModule(id: string, base: string) {
if (!ts.isExternalModuleNameRelative(id)) throw new Error(`Module '${id}' could not be found.`);
const file = vpath.resolve(base, id);
const module = loadAsFile(file) || loadAsDirectory(file);
if (!module) throw new Error(`Module '${id}' could not be found.`);
return module.exports;
}
function load(file: string) {
return requireModule(file, fs.cwd());
}
const loader = getLoader(compilerOptions, fs, globals);
return loader.import(output.file);
}
export function evaluateJavaScript(sourceText: string, globals?: Record<string, any>, sourceFile = sourceFileJs) {
globals = { Symbol: FakeSymbol, ...globals };
const fs = new vfs.FileSystem(/*ignoreCase*/ false, { files: { [sourceFile]: sourceText } });
return createLoader(fs, globals)(sourceFile);
return new CommonJsLoader(fs, globals).import(sourceFile);
}
function getLoader(compilerOptions: ts.CompilerOptions, fs: vfs.FileSystem, globals: Record<string, any>): Loader<unknown> {
const moduleKind = ts.getEmitModuleKind(compilerOptions);
switch (moduleKind) {
case ts.ModuleKind.UMD:
case ts.ModuleKind.CommonJS:
return new CommonJsLoader(fs, globals);
case ts.ModuleKind.System:
return new SystemLoader(fs, globals);
case ts.ModuleKind.AMD:
case ts.ModuleKind.None:
default:
throw new Error(`ModuleKind '${ts.ModuleKind[moduleKind]}' not supported by evaluator.`);
}
}
abstract class Loader<TModule> {
protected readonly fs: vfs.FileSystem;
protected readonly globals: Record<string, any>;
private moduleCache = new ts.Map<string, TModule>();
constructor(fs: vfs.FileSystem, globals: Record<string, any>) {
this.fs = fs;
this.globals = globals;
}
protected isFile(file: string) {
return this.fs.existsSync(file) && this.fs.statSync(file).isFile();
}
protected abstract evaluate(text: string, file: string, module: TModule): void;
protected abstract createModule(file: string): TModule;
protected abstract getExports(module: TModule): any;
protected load(file: string): TModule {
if (!ts.isExternalModuleNameRelative(file)) throw new Error(`Module '${file}' could not be found.`);
let module = this.moduleCache.get(file);
if (module) return module;
this.moduleCache.set(file, module = this.createModule(file));
try {
const sourceText = this.fs.readFileSync(file, "utf8");
this.evaluate(sourceText, file, module);
return module;
}
catch (e) {
this.moduleCache.delete(file);
throw e;
}
}
protected resolve(id: string, base: string) {
return vpath.resolve(base, id);
}
import(id: string, base = this.fs.cwd()) {
if (!ts.isExternalModuleNameRelative(id)) throw new Error(`Module '${id}' could not be found.`);
const file = this.resolve(id, base);
const module = this.load(file);
if (!module) throw new Error(`Module '${id}' could not be found.`);
return this.getExports(module);
}
}
interface CommonJSModule {
exports: any;
}
class CommonJsLoader extends Loader<CommonJSModule> {
private resolveAsFile(file: string) {
if (this.isFile(file)) return file;
if (this.isFile(file + ".js")) return file + ".js";
return undefined;
}
private resolveIndex(dir: string) {
const indexFile = vpath.resolve(dir, "index.js");
if (this.isFile(indexFile)) return indexFile;
return undefined;
}
private resolveAsDirectory(dir: string) {
const packageFile = vpath.resolve(dir, "package.json");
if (this.isFile(packageFile)) {
const text = this.fs.readFileSync(packageFile, "utf8");
const json = JSON.parse(text);
if (json.main) {
const main = vpath.resolve(dir, json.main);
const result = this.resolveAsFile(main) || this.resolveIndex(main);
if (result === undefined) throw new Error("Module not found");
}
}
return this.resolveIndex(dir);
}
protected resolve(id: string, base: string) {
const file = vpath.resolve(base, id);
const resolved = this.resolveAsFile(file) || this.resolveAsDirectory(file);
if (!resolved) throw new Error(`Module '${id}' could not be found.`);
return resolved;
}
protected createModule(): CommonJSModule {
return { exports: {} };
}
protected getExports(module: CommonJSModule) {
return module.exports;
}
protected evaluate(text: string, file: string, module: CommonJSModule): void {
const globalNames: string[] = [];
const globalArgs: any[] = [];
for (const name in this.globals) {
if (ts.hasProperty(this.globals, name)) {
globalNames.push(name);
globalArgs.push(this.globals[name]);
}
}
const base = vpath.dirname(file);
const localRequire = (id: string) => this.import(id, base);
const evaluateText = `(function (module, exports, require, __dirname, __filename, ${globalNames.join(", ")}) { ${text} })`;
// eslint-disable-next-line no-eval
const evaluateThunk = (void 0, eval)(evaluateText) as (module: any, exports: any, require: (id: string) => any, dirname: string, filename: string, ...globalArgs: any[]) => void;
evaluateThunk.call(this.globals, module, module.exports, localRequire, vpath.dirname(file), file, ...globalArgs);
}
}
interface SystemModule {
file: string;
exports: any;
hasExports: boolean;
state: SystemModuleState;
dependencies: SystemModule[];
dependers: SystemModule[];
setters: SystemModuleDependencySetter[];
requestedDependencies?: string[];
declaration?: SystemModuleDeclaration;
hasError?: boolean;
error?: any;
}
const enum SystemModuleState {
// Instantiation phases:
Uninstantiated,
Instantiated,
// Linker phases:
AddingDependencies,
AllDependenciesAdded,
AllDependenciesInstantiated,
WiringSetters,
Linked,
// Evaluation phases:
Evaluating,
Ready,
}
interface SystemModuleExporter {
<T>(name: string, value: T): T;
<T extends object>(values: T): T;
}
interface SystemModuleContext {
import: (id: string) => Promise<any>;
meta: any;
}
type SystemModuleRegisterCallback = (exporter: SystemModuleExporter, context: SystemModuleContext) => SystemModuleDeclaration;
type SystemModuleDependencySetter = (dependency: any) => void;
interface SystemModuleDeclaration {
setters: SystemModuleDependencySetter[];
execute: () => void;
}
interface SystemGlobal {
register(dependencies: string[], declare: SystemModuleRegisterCallback): void;
}
class SystemLoader extends Loader<SystemModule> {
protected createModule(file: string): SystemModule {
return {
file,
// eslint-disable-next-line no-null/no-null
exports: Object.create(/*o*/ null),
dependencies: [],
dependers: [],
setters: [],
hasExports: false,
state: SystemModuleState.Uninstantiated
};
}
protected getExports(module: SystemModule) {
if (module.state < SystemModuleState.Ready) {
this.resetDependers(module, []);
this.evaluateModule(module, []);
if (module.state < SystemModuleState.Ready) {
const error = new Error("Module graph could not be loaded");
this.handleError(module, error);
throw error;
}
}
if (module.hasError) {
throw module.error;
}
return module.exports;
}
private handleError(module: SystemModule, error: any) {
if (!module.hasError) {
module.hasError = true;
module.error = error;
module.state = SystemModuleState.Ready;
}
}
protected evaluate(text: string, _file: string, module: SystemModule): void {
const globalNames: string[] = [];
const globalArgs: any[] = [];
for (const name in this.globals) {
if (ts.hasProperty(this.globals, name)) {
globalNames.push(name);
globalArgs.push(this.globals[name]);
}
}
const localSystem: SystemGlobal = {
register: (dependencies, declare) => this.instantiateModule(module, dependencies, declare)
};
const evaluateText = `(function (System, ${globalNames.join(", ")}) { ${text} })`;
try {
// eslint-disable-next-line no-eval
const evaluateThunk = (void 0, eval)(evaluateText) as (System: any, ...globalArgs: any[]) => void;
evaluateThunk.call(this.globals, localSystem, ...globalArgs);
}
catch (e) {
this.handleError(module, e);
throw e;
}
}
private instantiateModule(module: SystemModule, dependencies: string[], registration?: SystemModuleRegisterCallback) {
function exporter<T>(name: string, value: T): T;
function exporter<T>(value: T): T;
function exporter<T>(...args: [string, T] | [T]) {
module.hasExports = true;
const name = args.length === 1 ? undefined : args[0];
const value = args.length === 1 ? args[0] : args[1];
if (name !== undefined) {
module.exports[name] = value;
}
else {
for (const name in value) {
module.exports[name] = value[name];
}
}
for (const setter of module.setters) {
setter(module.exports);
}
return value;
}
const context: SystemModuleContext = {
import: (_id) => { throw new Error("Dynamic import not implemented."); },
meta: {
url: ts.isUrl(module.file) ? module.file : `file:///${ts.normalizeSlashes(module.file).replace(/^\//, "").split("/").map(encodeURIComponent).join("/")}`
}
};
module.requestedDependencies = dependencies;
try {
module.declaration = registration?.(exporter, context);
module.state = SystemModuleState.Instantiated;
for (const depender of module.dependers) {
this.linkModule(depender);
}
this.linkModule(module);
}
catch (e) {
this.handleError(module, e);
throw e;
}
}
private linkModule(module: SystemModule) {
try {
for (;;) {
switch (module.state) {
case SystemModuleState.Uninstantiated: {
throw new Error("Module not yet instantiated");
}
case SystemModuleState.Instantiated: {
// Module has been instantiated, start requesting dependencies.
// Set state so that re-entry while adding dependencies does nothing.
module.state = SystemModuleState.AddingDependencies;
const base = vpath.dirname(module.file);
const dependencies = module.requestedDependencies || [];
for (const dependencyId of dependencies) {
const dependency = this.load(this.resolve(dependencyId, base));
module.dependencies.push(dependency);
dependency.dependers.push(module);
}
// All dependencies have been added, switch state
// to check whether all dependencies are instantiated
module.state = SystemModuleState.AllDependenciesAdded;
continue;
}
case SystemModuleState.AddingDependencies: {
// in the middle of adding dependencies for this module, do nothing
return;
}
case SystemModuleState.AllDependenciesAdded: {
// all dependencies have been added, advance state if all dependencies are instantiated.
for (const dependency of module.dependencies) {
if (dependency.state === SystemModuleState.Uninstantiated) {
return;
}
}
// indicate all dependencies are instantiated for this module.
module.state = SystemModuleState.AllDependenciesInstantiated;
// trigger links for dependers of this module.
for (const depender of module.dependers) {
this.linkModule(depender);
}
continue;
}
case SystemModuleState.AllDependenciesInstantiated: {
// all dependencies have been instantiated, start wiring setters
module.state = SystemModuleState.WiringSetters;
for (let i = 0; i < module.dependencies.length; i++) {
const dependency = module.dependencies[i];
const setter = module.declaration?.setters[i];
if (setter) {
dependency.setters.push(setter);
if (dependency.hasExports || dependency.state === SystemModuleState.Ready) {
// wire hoisted exports or ready dependencies.
setter(dependency.exports);
}
}
}
module.state = SystemModuleState.Linked;
// ensure graph is fully linked
for (const depender of module.dependers) {
this.linkModule(depender);
}
continue;
}
case SystemModuleState.WiringSetters: // in the middle of wiring setters for this module, nothing to do
case SystemModuleState.Linked: // module has already been linked, nothing to do
case SystemModuleState.Evaluating: // module is currently evaluating, nothing to do
case SystemModuleState.Ready: // module is done evaluating, nothing to do
return;
}
}
}
catch (e) {
this.handleError(module, e);
throw e;
}
}
private resetDependers(module: SystemModule, stack: SystemModule[]) {
if (stack.lastIndexOf(module) !== -1) {
return;
}
stack.push(module);
module.dependers.length = 0;
for (const dependency of module.dependencies) {
this.resetDependers(dependency, stack);
}
stack.pop();
}
private evaluateModule(module: SystemModule, stack: SystemModule[]) {
if (module.state < SystemModuleState.Linked) throw new Error("Invalid state for evaluation.");
if (module.state !== SystemModuleState.Linked) return;
if (stack.lastIndexOf(module) !== -1) {
// we are already evaluating this module
return;
}
stack.push(module);
module.state = SystemModuleState.Evaluating;
try {
for (const dependency of module.dependencies) {
this.evaluateModule(dependency, stack);
}
module.declaration?.execute?.();
module.state = SystemModuleState.Ready;
}
catch (e) {
this.handleError(module, e);
throw e;
}
}
}
}

View File

@@ -99,6 +99,7 @@
"unittests/evaluation/optionalCall.ts",
"unittests/evaluation/objectRest.ts",
"unittests/evaluation/superInStaticInitializer.ts",
"unittests/evaluation/updateExpressionInModule.ts",
"unittests/services/cancellableLanguageServiceOperations.ts",
"unittests/services/colorization.ts",
"unittests/services/convertToAsyncFunction.ts",

View File

@@ -0,0 +1,125 @@
describe("unittests:: evaluation:: updateExpressionInModule", () => {
// only run BigInt tests if BigInt is supported in the host environment
const itIfBigInt = typeof BigInt === "function" ? it : it.skip;
it("pre-increment in commonjs using Number", () => {
const result = evaluator.evaluateTypeScript({
files: {
"/.src/main.ts": `
let a = 1;
export { a };
export const b = ++a;
`
},
rootFiles: ["/.src/main.ts"],
main: "/.src/main.ts"
}, { module: ts.ModuleKind.CommonJS });
assert.equal(result.a, 2);
assert.equal(result.b, 2);
});
it("pre-increment in System using Number", () => {
const result = evaluator.evaluateTypeScript({
files: {
"/.src/main.ts": `
let a = 1;
export { a };
export const b = ++a;
`
},
rootFiles: ["/.src/main.ts"],
main: "/.src/main.ts"
}, { module: ts.ModuleKind.System });
assert.equal(result.a, 2);
assert.equal(result.b, 2);
});
itIfBigInt("pre-increment in commonjs using BigInt", () => {
const result = evaluator.evaluateTypeScript({
files: {
"/.src/main.ts": `
let a = BigInt(1);
export { a };
export const b = ++a;
`
},
rootFiles: ["/.src/main.ts"],
main: "/.src/main.ts"
}, { module: ts.ModuleKind.CommonJS }, { BigInt });
assert.equal(result.a, BigInt(2));
assert.equal(result.b, BigInt(2));
});
itIfBigInt("pre-increment in System using BigInt", () => {
const result = evaluator.evaluateTypeScript({
files: {
"/.src/main.ts": `
let a = BigInt(1);
export { a };
export const b = ++a;
`
},
rootFiles: ["/.src/main.ts"],
main: "/.src/main.ts"
}, { module: ts.ModuleKind.System }, { BigInt });
assert.equal(result.a, BigInt(2));
assert.equal(result.b, BigInt(2));
});
it("post-increment in commonjs using Number", () => {
const result = evaluator.evaluateTypeScript({
files: {
"/.src/main.ts": `
let a = 1;
export { a };
export const b = a++;
`
},
rootFiles: ["/.src/main.ts"],
main: "/.src/main.ts"
}, { module: ts.ModuleKind.CommonJS });
assert.equal(result.a, 2);
assert.equal(result.b, 1);
});
it("post-increment in System using Number", () => {
const result = evaluator.evaluateTypeScript({
files: {
"/.src/main.ts": `
let a = 1;
export { a };
export const b = a++;
`
},
rootFiles: ["/.src/main.ts"],
main: "/.src/main.ts"
}, { module: ts.ModuleKind.System });
assert.equal(result.a, 2);
assert.equal(result.b, 1);
});
itIfBigInt("post-increment in commonjs using BigInt", () => {
const result = evaluator.evaluateTypeScript({
files: {
"/.src/main.ts": `
let a = BigInt(1);
export { a };
export const b = a++;
`
},
rootFiles: ["/.src/main.ts"],
main: "/.src/main.ts"
}, { module: ts.ModuleKind.CommonJS }, { BigInt });
assert.equal(result.a, BigInt(2));
assert.equal(result.b, BigInt(1));
});
itIfBigInt("post-increment in System using BigInt", () => {
const result = evaluator.evaluateTypeScript({
files: {
"/.src/main.ts": `
let a = BigInt(1);
export { a };
export const b = a++;
`
},
rootFiles: ["/.src/main.ts"],
main: "/.src/main.ts"
}, { module: ts.ModuleKind.System }, { BigInt });
assert.equal(result.a, BigInt(2));
assert.equal(result.b, BigInt(1));
});
});

View File

@@ -9,7 +9,7 @@ var buzz = 10;
buzz += 3;
var bizz = 8;
bizz++; // compiles to exports.bizz = bizz += 1
bizz++; // compiles to exports.bizz = (bizz++, bizz)
bizz--; // similarly
++bizz; // compiles to exports.bizz = ++bizz
@@ -32,6 +32,6 @@ exports.buzz = buzz;
exports.buzz = buzz += 3;
var bizz = 8;
exports.bizz = bizz;
(exports.bizz = ++bizz) - 1; // compiles to exports.bizz = bizz += 1
(exports.bizz = --bizz) + 1; // similarly
exports.bizz = (bizz++, bizz); // compiles to exports.bizz = (bizz++, bizz)
exports.bizz = (bizz--, bizz); // similarly
exports.bizz = ++bizz; // compiles to exports.bizz = ++bizz

View File

@@ -20,7 +20,7 @@ buzz += 3;
var bizz = 8;
>bizz : Symbol(bizz, Decl(server.ts, 9, 3))
bizz++; // compiles to exports.bizz = bizz += 1
bizz++; // compiles to exports.bizz = (bizz++, bizz)
>bizz : Symbol(bizz, Decl(server.ts, 9, 3))
bizz--; // similarly

View File

@@ -30,7 +30,7 @@ var bizz = 8;
>bizz : number
>8 : 8
bizz++; // compiles to exports.bizz = bizz += 1
bizz++; // compiles to exports.bizz = (bizz++, bizz)
>bizz++ : number
>bizz : number

View File

@@ -18,21 +18,22 @@ export { x };
//// [moduleExportsUnaryExpression.js]
"use strict";
var _a, _b, _c, _d;
exports.__esModule = true;
exports.x = exports.foo = void 0;
var x = 1;
exports.x = x;
function foo(y) {
if (y <= (exports.x = ++x) - 1)
return y <= (exports.x = ++x) - 1;
if (y <= (exports.x = --x) + 1)
return y <= (exports.x = --x) + 1;
if (y <= (exports.x = (_a = x++, x), _a))
return y <= (exports.x = (_b = x++, x), _b);
if (y <= (exports.x = (_c = x--, x), _c))
return y <= (exports.x = (_d = x--, x), _d);
if (y <= (exports.x = ++x))
return y <= (exports.x = ++x);
if (y <= (exports.x = --x))
return y <= (exports.x = --x);
(exports.x = ++x) - 1;
(exports.x = --x) + 1;
exports.x = (x++, x);
exports.x = (x--, x);
exports.x = ++x;
exports.x = --x;
}

View File

@@ -41,8 +41,8 @@ System.register([], function (exports_1, context_1) {
setters: [],
execute: function () {
exports_1("x", x = 1);
exports_1("x", ++x) - 1;
exports_1("x", --x) + 1;
exports_1("x", (x++, x));
exports_1("x", (x--, x));
exports_1("x", ++x);
exports_1("x", --x);
exports_1("x", x += 1);
@@ -55,8 +55,8 @@ System.register([], function (exports_1, context_1) {
x - 1;
x & 1;
x | 1;
for (exports_1("x", x = 5);; exports_1("x", ++x) - 1) { }
for (exports_1("x", x = 8);; exports_1("x", --x) + 1) { }
for (exports_1("x", x = 5);; exports_1("x", (x++, x))) { }
for (exports_1("x", x = 8);; exports_1("x", (x--, x))) { }
for (exports_1("x", x = 15);; exports_1("x", ++x)) { }
for (exports_1("x", x = 18);; exports_1("x", --x)) { }
for (var x_1 = 50;;) { }

View File

@@ -12,7 +12,7 @@ var buzz = 10;
buzz += 3;
var bizz = 8;
bizz++; // compiles to exports.bizz = bizz += 1
bizz++; // compiles to exports.bizz = (bizz++, bizz)
bizz--; // similarly
++bizz; // compiles to exports.bizz = ++bizz