-
Notifications
You must be signed in to change notification settings - Fork 13.1k
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
| } | ||
|
|
||
| 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
| 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
| 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
| 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
| } | ||
|
|
||
| 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.
|
|
||
| export interface Decorator extends Node { | ||
| kind: SyntaxKind.Decorator; | ||
| parent?: NamedDeclaration; |
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
| 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. |
rbuckton
left a comment
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
| 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.posmay be undefined despite our best efforts to type it, so the loop continued forever, thinking it was inside of thetypeParametersof 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 functiongetPospromised to return anumberbut 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 likeonBeforeEmitNodeArrayto 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.