-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Ensure that emitter calls callbacks #18284
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
typeParameters: ReadonlyArray<TypeParameterDeclaration> | undefined, | ||
parameters: ReadonlyArray<ParameterDeclaration>, | ||
type: TypeNode | undefined, | ||
equalsGreaterThanTokenOrBody: Token<SyntaxKind.EqualsGreaterThanToken> | ConciseBody, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to ensure that we use the =>
from the new tree, which has a correct position. The =>
from the old tree has a different position that causes assertion errors in the formatter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add it at the end instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should always keep elements in the create/update methods in the order they are parsed.
src/compiler/emitter.ts
Outdated
emit(node.typeParameter.constraint); | ||
write("]"); | ||
|
||
if (onEmitNode) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We really should avoid calling onEmitNode
directly, and should be calling emit
instead, except in this case we need to emit a type parameter in a different fashion. For anyone writing a custom transform, they won't know whether their onEmitNode
is being called against a normal TypeParameter or the TypeParameter of a MappedType. That is the reason we have EmitHint
, as it allows us to provide a hint as to the context in which we are emitting a node.
I would recommend we add MappedTypeParameter
to the EmitHint
enum and add a branch to pipelineEmitWithHint
for that branch that calls emitMappedTypeParameter
. Then we can replace this code with a call to pipelineEmitWithNotification(EmitHint.MappedTypeParameter, node.typeParameter)
.
src/compiler/emitter.ts
Outdated
@@ -1114,6 +1115,14 @@ namespace ts { | |||
write("}"); | |||
} | |||
|
|||
function emitMappedTypeParameter(_hint: EmitHint, node: TypeParameterDeclaration) { | |||
write("["); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We parse the opening and closing brackets in parseMappedType
, so their tokens do not belong to the type parameter. Writing the [
and ]
tokens should happen in emitMappedType
.
src/compiler/emitter.ts
Outdated
@@ -1382,7 +1381,10 @@ namespace ts { | |||
} | |||
|
|||
function emitYieldExpression(node: YieldExpression) { | |||
write(node.asteriskToken ? "yield*" : "yield"); | |||
write("yield"); | |||
if (node.asteriskToken) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to guard against undefined, as that is already handled in emit
@@ -1662,7 +1664,11 @@ namespace ts { | |||
function emitFunctionDeclarationOrExpression(node: FunctionDeclaration | FunctionExpression) { | |||
emitDecorators(node, node.decorators); | |||
emitModifiers(node, node.modifiers); | |||
write(node.asteriskToken ? "function* " : "function "); | |||
write("function"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to guard against undefined as that is already handled in emit
Edit: I was incorrect. Apparently emit does not have this guard.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I get a test failure in the test PrinterAPI printFile removeComments
if I try to emit the asterisk unconditionally. emit
doesn't seem to check for an undefined node.
src/compiler/emitter.ts
Outdated
@@ -2395,6 +2399,17 @@ namespace ts { | |||
emitList(parentNode, parameters, ListFormat.IndexSignatureParameters); | |||
} | |||
|
|||
function emitSingleElementList(list: NodeArray<Node>) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you craft suitable ListFormat
bitmasks to use instead and just call emitList
where applicable instead of creating a special cased list emit function?
src/compiler/emitter.ts
Outdated
@@ -2580,16 +2595,20 @@ namespace ts { | |||
: writeTokenText(token, pos); | |||
} | |||
|
|||
function writeTokenNode(node: Node) { | |||
function writeTokenAndCallCallbacks(node: Node, text: string) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can simplify this by replacing all calls to writeIfPresent
with emit
, as emit
already guards against undefined, will in turn eventually call writeTokenNode
, and may give us better comment preservation and source-maps.
src/compiler/utilities.ts
Outdated
return array.hasOwnProperty("pos") | ||
&& array.hasOwnProperty("end"); | ||
const res = array.hasOwnProperty("pos") && array.hasOwnProperty("end"); | ||
if (res) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We possibly call this a lot between visitor and factory. Do the added checks have any perf impact?
495f1a2
to
7d931bb
Compare
49e21b1
to
5b44de1
Compare
src/compiler/factory.ts
Outdated
condition: Expression, | ||
whenTrue: Expression, | ||
whenFalse: Expression, | ||
questionToken: Token<SyntaxKind.QuestionToken> = node.questionToken, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should always keep these arguments in the same order they are parsed.
typeParameters: ReadonlyArray<TypeParameterDeclaration> | undefined, | ||
parameters: ReadonlyArray<ParameterDeclaration>, | ||
type: TypeNode | undefined, | ||
equalsGreaterThanTokenOrBody: Token<SyntaxKind.EqualsGreaterThanToken> | ConciseBody, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should always keep elements in the create/update methods in the order they are parsed.
src/compiler/factory.ts
Outdated
} | ||
body: ConciseBody, | ||
// Optional for backwards-compatibility only -- should always provide this. | ||
equalsGreaterThanToken: Token<SyntaxKind.EqualsGreaterThanToken> = node.equalsGreaterThanToken): ArrowFunction { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As complex as it was, the previous version was better. We should always keep these arguments in the same order as they would be parsed/visited.
src/compiler/types.ts
Outdated
@@ -4261,6 +4261,7 @@ namespace ts { | |||
SourceFile, // Emitting a SourceFile | |||
Expression, // Emitting an Expression | |||
IdentifierName, // Emitting an IdentifierName | |||
MappedTypeParameter, // Emitting a TypeParameterDeclaration inside of a MappedTypeNode |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: realign the surrounding comments
src/compiler/visitor.ts
Outdated
@@ -525,7 +525,9 @@ namespace ts { | |||
return updateConditional(<ConditionalExpression>node, | |||
visitNode((<ConditionalExpression>node).condition, visitor, isExpression), | |||
visitNode((<ConditionalExpression>node).whenTrue, visitor, isExpression), | |||
visitNode((<ConditionalExpression>node).whenFalse, visitor, isExpression)); | |||
visitNode((<ConditionalExpression>node).whenFalse, visitor, isExpression), | |||
visitNode((<ConditionalExpression>node).questionToken, visitor, n => n.kind === SyntaxKind.QuestionToken), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would just use isToken
rather than be overly concerned about the specific token. No need to allocate a new function object every time this is called.
src/compiler/visitor.ts
Outdated
visitNode((<ConditionalExpression>node).whenFalse, visitor, isExpression)); | ||
visitNode((<ConditionalExpression>node).whenFalse, visitor, isExpression), | ||
visitNode((<ConditionalExpression>node).questionToken, visitor, n => n.kind === SyntaxKind.QuestionToken), | ||
visitNode((<ConditionalExpression>node).colonToken, visitor, n => n.kind === SyntaxKind.ColonToken)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would just use isToken
rather than be overly concerned about the specific token. No need to allocate a new function object every time this is called.
src/compiler/visitor.ts
Outdated
visitNode((<ArrowFunction>node).equalsGreaterThanToken, visitor, n => n.kind === SyntaxKind.EqualsGreaterThanToken), | ||
visitFunctionBody((<ArrowFunction>node).body, visitor, context)); | ||
visitFunctionBody((<ArrowFunction>node).body, visitor, context), | ||
visitNode((<ArrowFunction>node).equalsGreaterThanToken, visitor, n => n.kind === SyntaxKind.EqualsGreaterThanToken)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would just use isToken
rather than be overly concerned about the specific token. No need to allocate a new function object every time this is called.
Ran the perf tests and saw no slowdown due to this PR. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd still rather not have emitSingleElementList if possible, but we can remove it later.
src/compiler/emitter.ts
Outdated
@@ -465,6 +474,12 @@ namespace ts { | |||
emitIdentifier(<Identifier>node); | |||
} | |||
|
|||
function pipelineEmitMappedTypeParameter(node: TypeParameterDeclaration): void { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just call this emitMappedTypeParameter.
* Ensure that emitter calls calbacks * Move new parameter to end of parameters * Fix for ConditionalExpression * Make suggested changes to emitter * Fix parameter ordering * Respond to minor comments * Remove potentially expensive assertion * More emitter cleanup
@Andy-MS we will need to port this to release-2.5 |
* Ensure that emitter calls calbacks * Move new parameter to end of parameters * Fix for ConditionalExpression * Make suggested changes to emitter * Fix parameter ordering * Respond to minor comments * Remove potentially expensive assertion * More emitter cleanup
Fixes #18091
The bug was happening because in
formatting.ts
, inprocessChildNodes
, we continue scanning until getting tonodes.pos
. Since this is a dynamically typed language,nodes.pos
may be undefined despite our best efforts to type it, so the loop continued forever, thinking it was inside of thetypeParameters
of the generated function declaration; which meant it didn't know that it was actually nested several levels deep and should have been indenting more.The problem was that in
textChanges.ts
, the functiongetPos
promised to return anumber
but was implemented with(<any>n)["__pos"]
, which wasn't always defined. I added an assertion that it was, which brought up more problems.In
textChanges
, we rely on callbacks likeonBeforeEmitNodeArray
to set"__pos"
. But there were many situations in which the emitter did not call these callbacks, so those all had to be updated; otherwise we don't set"__pos"
, and then the new nodes have missing or incorrect positions.