-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Add wrapper to emit statics/decorators inside es5 class #16120
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
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.
(Note: I didn't review changes to .sourcemap.txt
baselines)
return value !== undefined && test(value) ? value : undefined; | ||
} | ||
|
||
export function cast<TOut extends TIn, TIn = any>(value: TIn | undefined, test: (value: TIn) => value is TOut): TOut { |
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.
Don't see any reason to allow an undefined
parameter since the function will always fail in that case.
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.
If we ever turn on --strictNullChecks
there may be cases where value
may be typed undefined
.
src/compiler/utilities.ts
Outdated
return false; | ||
} | ||
|
||
const returnStatement = tryCast(elementAt(statements, isVariableStatement(lastOrUndefined(statements)) ? -2 : -1), isReturnStatement); |
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.
This could be just:
const returnStatement = statements[statements.length - (isVariableStatement(lastOrUndefined(statements)) ? 2 : 1)];
return isReturnStatement(returnStatement) &&
returnStatement.expression &&
isIdentifier(skipOuterExpressions(returnStatement.expression));
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 still like this better as just return condition
instead of if (!condition) { return false; } return true;
src/compiler/factory.ts
Outdated
@@ -3714,6 +3736,21 @@ namespace ts { | |||
All = Parentheses | Assertions | PartiallyEmittedExpressions | |||
} | |||
|
|||
export type OuterExpression = ParenthesizedExpression | TypeAssertion | AsExpression | PartiallyEmittedExpression; |
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.
Why not NonNullExpression
?
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.
It didn't exist when skipOuterExpressions
was initially added.
src/compiler/utilities.ts
Outdated
return false; | ||
} | ||
|
||
const func = <FunctionExpression>skipParentheses((<CallExpression>node).expression); |
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.
What makes this cast valid?
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.
isImmediatelyInvokedFunctionExpression
already ensured the node has the correct shape.
|
||
// We skip any outer expressions in a number of places to get to the innermost | ||
// expression, but we will restore them later to preserve comments and source maps. | ||
const body = cast(skipOuterExpressions(node.expression), isFunctionExpression).body; |
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.
In isTypeScriptClassWrapper
, this just skips parentheses.
@@ -3074,6 +3074,7 @@ namespace ts { | |||
export interface Type { | |||
flags: TypeFlags; // Flags | |||
/* @internal */ id: number; // Unique ID | |||
/* @internal */ checker: TypeChecker; |
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.
This should definitely be documented as only being present in debug mode... better, don't define it here at all and require a cast to DebugType
.
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.
It's not only present in debug mode. It's also present when using the language service. See https://github.com/Microsoft/TypeScript/blob/master/src/services/services.ts#L382
@@ -3977,34 +3978,34 @@ namespace ts { | |||
} | |||
|
|||
export const enum EmitFlags { | |||
SingleLine = 1 << 0, // The contents of this node should be emitted on a single line. |
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.
What's the point of the whitespace change here? It seems arbitrary how much to use.
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.
Trying to keep comments aligned for readability, except where the enum member is significantly long.
src/compiler/utilities.ts
Outdated
@@ -850,10 +850,88 @@ namespace ts { | |||
return node && (node.kind === SyntaxKind.GetAccessor || node.kind === SyntaxKind.SetAccessor); | |||
} | |||
|
|||
export function isClassLike(node: Node): node is ClassLikeDeclaration { | |||
export function isClassLike(node: Node | undefined): node is ClassLikeDeclaration { |
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 rather see type predicates like this take defined inputs. I have a PR (#16121) making this public and would prefer to keep just Node
as the parameter type; we can keep the node &&
in the body for now, but if we ever start using --strictNullChecks
it should become the caller's responsibility.
src/compiler/utilities.ts
Outdated
return node && (node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression); | ||
} | ||
|
||
export function isImmediatelyInvokedFunctionExpression(node: 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.
No need to export. isTypeScriptClassWrapper
could also be moved closer to its use.
src/compiler/utilities.ts
Outdated
} | ||
|
||
const syntaxKindCache: string[] = []; | ||
export function formatSyntaxKind(kind: SyntaxKind): 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.
If this is just for debugging, it shouldn't need a cache?
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.
Its for debugging, yes. It probably doesn't need one, no.
src/compiler/transformers/ts.ts
Outdated
HasStaticInitializedProperties = 1 << 0, | ||
HasConstructorDecorators = 1 << 1, | ||
HasMemberDecorators = 1 << 2, | ||
IsNamespaceExport = 1 << 3, |
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.
or name IsTSNamespaceExport
|
||
HasAnyDecorators = HasConstructorDecorators | HasMemberDecorators, | ||
NeedsName = HasStaticInitializedProperties | HasMemberDecorators, | ||
MayNeedImmediatelyInvokedFunctionExpression = HasAnyDecorators | HasStaticInitializedProperties, |
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: NeedImmediatelyInvokedFunctionExpression
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.
These two flags indicates that the class may need an IIFE. Whether it actually needs it is also based on the target language, which is not encoded in these flags.
} | ||
|
||
// Write any decorators of the node. | ||
addClassElementDecorationStatements(statements, node, /*isStatic*/ false); | ||
addClassElementDecorationStatements(statements, node, /*isStatic*/ true); | ||
addConstructorDecorationStatement(statements, node); | ||
|
||
if (facts & ClassFacts.UseImmediatelyInvokedFunctionExpression) { |
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: add comment show the transform result
*/ | ||
function createClassDeclarationHeadWithoutDecorators(node: ClassDeclaration, name: Identifier, hasExtendsClause: boolean, hasStaticProperties: boolean) { | ||
function createClassDeclarationHeadWithoutDecorators(node: ClassDeclaration, name: Identifier, facts: ClassFacts) { | ||
// ${modifiers} class ${name} ${heritageClauses} { |
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.
Comments here seems to be outdated since we not always emit modifiers
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.
It just illustrates an example of the emit. I'm not too terribly concerned.
src/compiler/transformers/ts.ts
Outdated
@@ -613,9 +676,9 @@ namespace ts { | |||
* @param statements A statement list to which to add the declaration. | |||
* @param node A ClassDeclaration node. | |||
* @param name The name of the class. | |||
* @param hasExtendsClause A value indicating whether the class has an extends clause. | |||
* @param facts Precomputed facts about the clas. |
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.
Though i think it is useful to keep since these comments surface up in quick info
@@ -2130,6 +2130,24 @@ namespace ts { | |||
|
|||
// Compound nodes | |||
|
|||
export function createImmediatelyInvokedFunctionExpression(statements: Statement[]): CallExpression; | |||
export function createImmediatelyInvokedFunctionExpression(statements: Statement[], param: ParameterDeclaration, paramValue: Expression): CallExpression; |
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: should param
and paramValue
be default value of []?
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, these are not arrays. As written, this function only allows zero or one parameter.
src/compiler/transformers/es2015.ts
Outdated
const variable = varStatement.declarationList.declarations[0]; | ||
const initializer = skipOuterExpressions(variable.initializer); | ||
|
||
// Under certain conditions, the 'ts' transformer may may introduce a class alias, which |
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.
he 'ts' transformer may may
is he 'ts' transformer may
@@ -3262,6 +3319,160 @@ namespace ts { | |||
); | |||
} | |||
|
|||
function visitTypeScriptClassWrapper(node: CallExpression) { | |||
// This is a call to a class wrapper function (an IIFE) created by the 'ts' transformer. |
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.
great comment 👍
This PR changes our class emit when targeting ES5 to emit static initializers and decorators inside of the ES5 class IIFE.
This is accomplished by first wrapping the ES6 class and related statements inside of an IIFE in the ts transformer, then unwrapping the IIFE and merging it in the es2015 transformer.
This PR also adds some diagnostic information to
Node
andType
that is only exposed when debugging, to assist with debugging the compiler. This also adds the capability to removeDebug.assert
,Debug.fail
and related debugging assertions from the call stack on NodeJS to better interpret where an assertion failure occurred when running tests (as our test harness sets the stack trace limit to 1).Fixes #15857