Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3238,6 +3238,11 @@ namespace ts {
transformFlags |= TransformFlags.ContainsPropertyInitializer;
}

// Private names are an ESNext feature.
if (isIdentifier(node.name) && node.name.isPrivateName) {
transformFlags |= TransformFlags.AssertESNext;
}

node.transformFlags = transformFlags | TransformFlags.HasComputedFlags;
return transformFlags & ~TransformFlags.NodeExcludes;
}
Expand Down Expand Up @@ -3375,6 +3380,11 @@ namespace ts {
transformFlags |= TransformFlags.ContainsSuper;
}

// Private names are an ESNext feature.
if (isIdentifier(node.name) && node.name.isPrivateName) {
transformFlags |= TransformFlags.AssertESNext;
}

node.transformFlags = transformFlags | TransformFlags.HasComputedFlags;
return transformFlags & ~TransformFlags.PropertyAccessExcludes;
}
Expand Down
5 changes: 1 addition & 4 deletions src/compiler/scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1725,10 +1725,7 @@ namespace ts {
return token = SyntaxKind.Unknown;
case CharacterCodes.hash:
pos++;
if (
languageVersion === ScriptTarget.ESNext
&& isIdentifierStart(ch = text.charCodeAt(pos), languageVersion)
) {
if (isIdentifierStart(ch = text.charCodeAt(pos), languageVersion)) {
tokenFlags |= TokenFlags.PrivateName;
pos++;
while (pos < end && isIdentifierPart(ch = text.charCodeAt(pos), languageVersion)) pos++;
Expand Down
12 changes: 10 additions & 2 deletions src/compiler/transformers/es2015.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3306,11 +3306,14 @@ namespace ts {

// The class statements are the statements generated by visiting the first statement with initializer of the
// body (1), while all other statements are added to remainingStatements (2)
const isVariableStatementWithInitializer = (stmt: Statement) => isVariableStatement(stmt) && !!first(stmt.declarationList.declarations).initializer;
const isVariableStatementWithInitializer = (stmt: Statement) => !isEndOfDeclarationMarker(stmt) &&
isVariableStatement(stmt) && !!first(stmt.declarationList.declarations).initializer;
const isEndOfDeclarationMarker = (stmt: Statement) => stmt.kind === SyntaxKind.EndOfDeclarationMarker;
const bodyStatements = visitNodes(body.statements, visitor, isStatement);
const classStatements = filter(bodyStatements, isVariableStatementWithInitializer);
const remainingStatements = filter(bodyStatements, stmt => !isVariableStatementWithInitializer(stmt));
const varStatement = cast(first(classStatements), isVariableStatement);
const endOfDeclarationMarkers = filter(bodyStatements, isEndOfDeclarationMarker);

// We know there is only one variable declaration here as we verified this in an
// earlier call to isTypeScriptClassWrapper
Expand Down Expand Up @@ -3382,12 +3385,17 @@ namespace ts {
addRange(statements, funcStatements, classBodyEnd + 1);
}

// Add other class statements (such as the WeakMap declarations output by the 'esnext'
// transformer for private names).
addRange(statements, classStatements, /*start*/ 1);

// Add the remaining statements of the outer wrapper.
addRange(statements, remainingStatements);

// The 'es2015' class transform may add an end-of-declaration marker. If so we will add it
// after the remaining statements from the 'ts' transformer.
addRange(statements, classStatements, /*start*/ 1);
addRange(statements, endOfDeclarationMarkers);


// Recreate any outer parentheses or partially-emitted expressions to preserve source map
// and comment locations.
Expand Down
263 changes: 263 additions & 0 deletions src/compiler/transformers/esnext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,19 @@ namespace ts {
let enclosingFunctionFlags: FunctionFlags;
let enclosingSuperContainerFlags: NodeCheckFlags = 0;


/**
* Maps private names to the generated name of the WeakMap.
*/
interface PrivateNameEnvironment {
[name: string]: {
weakMap: Identifier;
initializer?: Expression;
};
}
const privateNameEnvironmentStack: PrivateNameEnvironment[] = [];
let privateNameEnvironmentIndex = -1;

return chainBundle(transformSourceFile);

function transformSourceFile(node: SourceFile) {
Expand Down Expand Up @@ -101,11 +114,200 @@ namespace ts {
return visitParenthesizedExpression(node as ParenthesizedExpression, noDestructuringValue);
case SyntaxKind.CatchClause:
return visitCatchClause(node as CatchClause);
case SyntaxKind.PropertyAccessExpression:
return visitPropertyAccessExpression(node as PropertyAccessExpression);
case SyntaxKind.ClassDeclaration:
return visitClassDeclaration(node as ClassDeclaration);
case SyntaxKind.ClassExpression:
return visitClassExpression(node as ClassExpression);
default:
return visitEachChild(node, visitor, context);
}
}

function currentPrivateNameEnvironment() {
return privateNameEnvironmentStack[privateNameEnvironmentIndex];
}

function addPrivateName(name: Identifier, initializer?: Expression) {
const environment = currentPrivateNameEnvironment();
const nameString = getTextOfIdentifierOrLiteral(name);
if (nameString in environment) {
throw new Error("Redeclaring private name " + nameString + ".");
}
const weakMap = createFileLevelUniqueName("_" + nameString.substring(1));
environment[nameString] = {
weakMap,
initializer
};
return weakMap;
}

function accessPrivateName(name: Identifier) {
const environment = currentPrivateNameEnvironment();
const nameString = getTextOfIdentifierOrLiteral(name);
if (nameString in environment) {
return environment[nameString].weakMap;
}
throw new Error("Accessing undeclared private name.");
}

function visitPropertyAccessExpression(node: PropertyAccessExpression): Expression {
if (node.name.isPrivateName) {
const weakMapName = accessPrivateName(node.name);
return setOriginalNode(
setTextRange(
createClassPrivateFieldGetHelper(context, node.expression, weakMapName),
/* location */ node
),
node
);
}
return visitEachChild(node, visitor, context);
}

function visitorCollectPrivateNames(node: Node): VisitResult<Node> {
if (isPropertyDeclaration(node) && isIdentifier(node.name) && node.name.isPrivateName) {
addPrivateName(node.name, node.initializer);
return undefined;
}
// Don't collect private names from nested classes.
if (isClassLike(node)) {
return node;
}
return visitEachChild(node, visitorCollectPrivateNames, context);
}

function visitClassDeclaration(node: ClassDeclaration) {
startPrivateNameEnvironment();
node = visitEachChild(node, visitorCollectPrivateNames, context);
node = visitEachChild(node, visitor, context);
const statements = createPrivateNameWeakMapDeclarations(
currentPrivateNameEnvironment()
);
if (statements.length) {
node = updateClassDeclaration(
node,
node.decorators,
node.modifiers,
node.name,
node.typeParameters,
node.heritageClauses,
transformClassMembers(node.members)
);
}
prependStatements(statements, [node]);
endPrivateNameEnvironment();
return statements;
}

function visitClassExpression(node: ClassExpression) {
startPrivateNameEnvironment();
node = visitEachChild(node, visitorCollectPrivateNames, context);
node = visitEachChild(node, visitor, context);
const expressions = createPrivateNameWeakMapAssignments(
currentPrivateNameEnvironment()
);
if (expressions.length) {
node = updateClassExpression(
node,
node.modifiers,
node.name,
node.typeParameters,
node.heritageClauses,
transformClassMembers(node.members)
);
}
expressions.push(node);
endPrivateNameEnvironment();
return expressions.length > 1 ? createCommaList(expressions) : expressions[0];
}

function startPrivateNameEnvironment() {
// Create private name environment.
privateNameEnvironmentStack[++privateNameEnvironmentIndex] = {};
return currentPrivateNameEnvironment();
}

function endPrivateNameEnvironment(): PrivateNameEnvironment {
const privateNameEnvironment = currentPrivateNameEnvironment();
// Destroy private name environment.
delete privateNameEnvironmentStack[privateNameEnvironmentIndex--];
return privateNameEnvironment;
}

function createPrivateNameWeakMapDeclarations(environment: PrivateNameEnvironment): Statement[] {
return Object.keys(environment).map(name => {
const privateName = environment[name];
return createVariableStatement(
/* modifiers */ undefined,
[createVariableDeclaration(privateName.weakMap,
/* typeNode */ undefined,
createNew(
createIdentifier("WeakMap"),
/* typeArguments */ undefined,
/* argumentsArray */ undefined
))]
);
});
}

function createPrivateNameWeakMapAssignments(environment: PrivateNameEnvironment): Expression[] {
return Object.keys(environment).map(name => {
const privateName = environment[name];
hoistVariableDeclaration(privateName.weakMap);
return createBinary(
privateName.weakMap,
SyntaxKind.EqualsToken,
createNew(createIdentifier("WeakMap"), /* typeArguments */ undefined, /* argumentsArray */ undefined)
);
});
}

function transformClassMembers(members: ReadonlyArray<ClassElement>): ClassElement[] {
// Rewrite constructor with private name initializers.
const privateNameEnvironment = currentPrivateNameEnvironment();
// Initialize private properties.
const initializerStatements = Object.keys(privateNameEnvironment).map(name => {
const privateName = privateNameEnvironment[name];
return createStatement(
createCall(
createPropertyAccess(privateName.weakMap, "set"),
/* typeArguments */ undefined,
[createThis(), privateName.initializer || createVoidZero()]
)
);
});
const ctor = find(
members,
(member) => isConstructorDeclaration(member) && !!member.body
) as ConstructorDeclaration | undefined;
if (ctor) {
const body = updateBlock(ctor.body!, [...initializerStatements, ...ctor.body!.statements]);
return members.map(member => {
if (member === ctor) {
return updateConstructor(
ctor,
ctor.decorators,
ctor.modifiers,
ctor.parameters,
body
);
}
return member;
});
}
return [
createConstructor(
/* decorators */ undefined,
/* modifiers */ undefined,
/* parameters */ [],
createBlock(initializerStatements)
),
...members
];
}

function visitAwaitExpression(node: AwaitExpression): Expression {
if (enclosingFunctionFlags & FunctionFlags.Async && enclosingFunctionFlags & FunctionFlags.Generator) {
return setOriginalNode(
Expand Down Expand Up @@ -266,6 +468,45 @@ namespace ts {
visitNode(node.right, noDestructuringValue ? visitorNoDestructuringValue : visitor, isExpression)
);
}
else if (isAssignmentOperator(node.operatorToken.kind) &&
isPropertyAccessExpression(node.left) &&
isIdentifier(node.left.name) &&
node.left.name.isPrivateName) {

const weakMapName = accessPrivateName(node.left.name);
if (isCompoundAssignment(node.operatorToken.kind)) {
let setReceiver: Expression;
let getReceiver: Identifier;
if (!isIdentifier(node.left.expression) && !isKeyword(node.left.expression.kind)) {
getReceiver = createTempVariable(/* recordTempVariable */ undefined);
hoistVariableDeclaration(getReceiver);
setReceiver = createBinary(getReceiver, SyntaxKind.EqualsToken, node.left.expression);
}
else {
getReceiver = node.left.expression as Identifier;
setReceiver = node.left.expression as Identifier;
}
return setOriginalNode(
createClassPrivateFieldSetHelper(
context,
setReceiver,
weakMapName,
createBinary(
createClassPrivateFieldGetHelper(context, getReceiver, weakMapName),
getOperatorForCompoundAssignment(node.operatorToken.kind),
node.right
)
),
node
);
}
else {
return setOriginalNode(
createClassPrivateFieldSetHelper(context, node.left.expression, weakMapName, node.right),
node
);
}
}
return visitEachChild(node, visitor, context);
}

Expand Down Expand Up @@ -917,6 +1158,28 @@ namespace ts {
);
}

const classPrivateFieldGetHelper: EmitHelper = {
name: "typescript:classPrivateFieldGet",
scoped: false,
text: `var _classPrivateFieldGet = function (receiver, privateMap) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to get private field on non-instance"); } return privateMap.get(receiver); };`
};

function createClassPrivateFieldGetHelper(context: TransformationContext, receiver: Expression, privateField: Identifier) {
context.requestEmitHelper(classPrivateFieldGetHelper);
return createCall(getHelperName("_classPrivateFieldGet"), /* typeArguments */ undefined, [ receiver, privateField ]);
}

const classPrivateFieldSetHelper: EmitHelper = {
name: "typescript:classPrivateFieldSet",
scoped: false,
text: `var _classPrivateFieldSet = function (receiver, privateMap, value) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to set private field on non-instance"); } privateMap.set(receiver, value); return value; };`
};

function createClassPrivateFieldSetHelper(context: TransformationContext, receiver: Expression, privateField: Identifier, value: Expression) {
context.requestEmitHelper(classPrivateFieldSetHelper);
return createCall(getHelperName("_classPrivateFieldSet"), /* typeArguments */ undefined, [ receiver, privateField, value ]);
}

const awaitHelper: EmitHelper = {
name: "typescript:await",
scoped: false,
Expand Down
22 changes: 0 additions & 22 deletions src/compiler/transformers/generators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -667,28 +667,6 @@ namespace ts {
}
}

function isCompoundAssignment(kind: BinaryOperator): kind is CompoundAssignmentOperator {
return kind >= SyntaxKind.FirstCompoundAssignment
&& kind <= SyntaxKind.LastCompoundAssignment;
}

function getOperatorForCompoundAssignment(kind: CompoundAssignmentOperator): BitwiseOperatorOrHigher {
switch (kind) {
case SyntaxKind.PlusEqualsToken: return SyntaxKind.PlusToken;
case SyntaxKind.MinusEqualsToken: return SyntaxKind.MinusToken;
case SyntaxKind.AsteriskEqualsToken: return SyntaxKind.AsteriskToken;
case SyntaxKind.AsteriskAsteriskEqualsToken: return SyntaxKind.AsteriskAsteriskToken;
case SyntaxKind.SlashEqualsToken: return SyntaxKind.SlashToken;
case SyntaxKind.PercentEqualsToken: return SyntaxKind.PercentToken;
case SyntaxKind.LessThanLessThanEqualsToken: return SyntaxKind.LessThanLessThanToken;
case SyntaxKind.GreaterThanGreaterThanEqualsToken: return SyntaxKind.GreaterThanGreaterThanToken;
case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: return SyntaxKind.GreaterThanGreaterThanGreaterThanToken;
case SyntaxKind.AmpersandEqualsToken: return SyntaxKind.AmpersandToken;
case SyntaxKind.BarEqualsToken: return SyntaxKind.BarToken;
case SyntaxKind.CaretEqualsToken: return SyntaxKind.CaretToken;
}
}

/**
* Visits a right-associative binary expression containing `yield`.
*
Expand Down
Loading