PR Feedback

This commit is contained in:
Ron Buckton 2017-05-30 12:34:37 -07:00
parent b8ee1691af
commit b69afd16dc
5 changed files with 150 additions and 158 deletions

View File

@ -740,6 +740,14 @@ namespace ts {
return to;
}
/**
* Gets the actual offset into an array for a relative offset. Negative offsets indicate a
* position offset from the end of the array.
*/
function toOffset(array: any[], offset: number) {
return offset < 0 ? array.length + offset : offset;
}
/**
* Appends a range of value to an array, returning the array.
*
@ -747,11 +755,19 @@ namespace ts {
* is created if `value` was appended.
* @param from The values to append to the array. If `from` is `undefined`, nothing is
* appended. If an element of `from` is `undefined`, that element is not appended.
* @param start The offset in `from` at which to start copying values.
* @param end The offset in `from` at which to stop copying values (non-inclusive).
*/
export function addRange<T>(to: T[] | undefined, from: T[] | undefined): T[] | undefined {
export function addRange<T>(to: T[] | undefined, from: T[] | undefined, start?: number, end?: number): T[] | undefined {
if (from === undefined) return to;
for (const v of from) {
to = append(to, v);
if (to === undefined) return from.slice(start, end);
start = start === undefined ? 0 : toOffset(from, start);
end = end === undefined ? from.length : toOffset(from, end);
for (let i = start; i < end && i < from.length; i++) {
const v = from[i];
if (v !== undefined) {
to.push(from[i]);
}
}
return to;
}
@ -781,9 +797,13 @@ namespace ts {
* A negative offset indicates the element should be retrieved from the end of the array.
*/
export function elementAt<T>(array: T[] | undefined, offset: number): T | undefined {
return array && array.length > 0 && (offset < 0 ? ~offset : offset) < array.length
? array[offset < 0 ? array.length + offset : offset]
: undefined;
if (array) {
offset = toOffset(array, offset);
if (offset < array.length) {
return array[offset];
}
}
return undefined;
}
/**

View File

@ -3736,7 +3736,7 @@ namespace ts {
All = Parentheses | Assertions | PartiallyEmittedExpressions
}
export type OuterExpression = ParenthesizedExpression | TypeAssertion | AsExpression | PartiallyEmittedExpression;
export type OuterExpression = ParenthesizedExpression | TypeAssertion | AsExpression | NonNullExpression | PartiallyEmittedExpression;
export function isOuterExpression(node: Node, kinds = OuterExpressionKinds.All): node is OuterExpression {
switch (node.kind) {
@ -3744,6 +3744,7 @@ namespace ts {
return (kinds & OuterExpressionKinds.Parentheses) !== 0;
case SyntaxKind.TypeAssertionExpression:
case SyntaxKind.AsExpression:
case SyntaxKind.NonNullExpression:
return (kinds & OuterExpressionKinds.Assertions) !== 0;
case SyntaxKind.PartiallyEmittedExpression:
return (kinds & OuterExpressionKinds.PartiallyEmittedExpressions) !== 0;
@ -3787,8 +3788,8 @@ namespace ts {
export function skipAssertions(node: Expression): Expression;
export function skipAssertions(node: Node): Node;
export function skipAssertions(node: Node): Node {
while (isAssertionExpression(node)) {
node = (<AssertionExpression>node).expression;
while (isAssertionExpression(node) || node.kind === SyntaxKind.NonNullExpression) {
node = (<AssertionExpression | NonNullExpression>node).expression;
}
return node;
@ -3809,6 +3810,7 @@ namespace ts {
case SyntaxKind.ParenthesizedExpression: return updateParen(outerExpression, expression);
case SyntaxKind.TypeAssertionExpression: return updateTypeAssertion(outerExpression, outerExpression.type, expression);
case SyntaxKind.AsExpression: return updateAsExpression(outerExpression, expression, outerExpression.type);
case SyntaxKind.NonNullExpression: return updateNonNullExpression(outerExpression, expression);
case SyntaxKind.PartiallyEmittedExpression: return updatePartiallyEmittedExpression(outerExpression, expression);
}
}

View File

@ -339,6 +339,58 @@ namespace ts {
&& !(<ReturnStatement>node).expression;
}
function isClassLikeVariableStatement(node: Node) {
if (!isVariableStatement(node)) return false;
const variable = singleOrUndefined((<VariableStatement>node).declarationList.declarations);
return variable
&& variable.initializer
&& isIdentifier(variable.name)
&& (isClassLike(variable.initializer)
|| (isAssignmentExpression(variable.initializer)
&& isIdentifier(variable.initializer.left)
&& isClassLike(variable.initializer.right)));
}
function isTypeScriptClassWrapper(node: Node) {
const call = tryCast(node, isCallExpression);
if (!call || isParseTreeNode(call) ||
some(call.typeArguments) ||
some(call.arguments)) {
return false;
}
const func = tryCast(skipOuterExpressions(call.expression), isFunctionExpression);
if (!func || isParseTreeNode(func) ||
some(func.typeParameters) ||
some(func.parameters) ||
func.type ||
!func.body) {
return false;
}
const statements = func.body.statements;
if (statements.length < 2) {
return false;
}
const firstStatement = statements[0];
if (isParseTreeNode(firstStatement) ||
!isClassLike(firstStatement) &&
!isClassLikeVariableStatement(firstStatement)) {
return false;
}
const lastStatement = elementAt(statements, -1);
const returnStatement = tryCast(isVariableStatement(lastStatement) ? elementAt(statements, -2) : lastStatement, isReturnStatement);
if (!returnStatement ||
!returnStatement.expression ||
!isIdentifier(skipOuterExpressions(returnStatement.expression))) {
return false;
}
return true;
}
function shouldVisitNode(node: Node): boolean {
return (node.transformFlags & TransformFlags.ContainsES2015) !== 0
|| convertedLoopState !== undefined
@ -3326,23 +3378,24 @@ namespace ts {
// var C_1;
// }())
//
const aliasAssignment = isAssignmentExpression(initializer) ? initializer : undefined;
const aliasAssignment = tryCast(initializer, isAssignmentExpression);
// The underlying call (3) is another IIFE that may contain a '_super' argument.
const call = cast(aliasAssignment ? skipOuterExpressions(aliasAssignment.right) : initializer, isCallExpression);
const func = cast(skipOuterExpressions(call.expression), isFunctionExpression);
// When we extract the statements of the inner IIFE, we exclude the 'return' statement (4)
// as we already have one that has been introduced by the 'ts' transformer.
const funcStatements = func.body.statements.slice(0, -1);
const funcStatements = func.body.statements;
let classBodyStart = 0;
let classBodyEnd = -1;
const statements: Statement[] = [];
if (aliasAssignment) {
// If we have a class alias assignment, we need to move it to the down-level constructor
// function we generated for the class.
const hasExtendsCall = isExpressionStatement(funcStatements[0]);
if (hasExtendsCall) {
statements.push(funcStatements[0]);
const extendsCall = tryCast(funcStatements[classBodyStart], isExpressionStatement);
if (extendsCall) {
statements.push(extendsCall);
classBodyStart++;
}
// We reuse the comment and source-map positions from the original variable statement
@ -3359,25 +3412,37 @@ namespace ts {
updateBinary(aliasAssignment,
aliasAssignment.left,
convertFunctionDeclarationToExpression(
cast(funcStatements[hasExtendsCall ? 1 : 0], isFunctionDeclaration)
cast(funcStatements[classBodyStart], isFunctionDeclaration)
)
)
)
])
)
);
addRange(statements, funcStatements.slice(hasExtendsCall ? 2 : 1));
}
else {
addRange(statements, funcStatements);
classBodyStart++;
}
// Find the trailing 'return' statement (4)
while (!isReturnStatement(elementAt(funcStatements, classBodyEnd))) {
classBodyEnd--;
}
// When we extract the statements of the inner IIFE, we exclude the 'return' statement (4)
// as we already have one that has been introduced by the 'ts' transformer.
addRange(statements, funcStatements, classBodyStart, classBodyEnd);
if (classBodyEnd < -1) {
// If there were any hoisted declarations following the return statement, we should
// append them.
addRange(statements, funcStatements, classBodyEnd + 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.slice(1));
addRange(statements, classStatements, /*start*/ 1);
// Recreate any outer parentheses or partially-emitted expressions to preserve source map
// and comment locations.
@ -3952,7 +4017,6 @@ namespace ts {
return false;
}
if (isClassElement(currentNode) && currentNode.parent === declaration) {
// we are in the class body, but we treat static fields as outside of the class body
return true;
}
currentNode = currentNode.parent;

View File

@ -19,10 +19,11 @@ namespace ts {
}
const enum ClassFacts {
None = 0,
HasStaticInitializedProperties = 1 << 0,
HasConstructorDecorators = 1 << 1,
HasMemberDecorators = 1 << 2,
IsNamespaceExport = 1 << 3,
IsExportOfNamespace = 1 << 3,
IsNamedExternalExport = 1 << 4,
IsDefaultExternalExport = 1 << 5,
HasExtendsClause = 1 << 6,
@ -31,7 +32,7 @@ namespace ts {
HasAnyDecorators = HasConstructorDecorators | HasMemberDecorators,
NeedsName = HasStaticInitializedProperties | HasMemberDecorators,
MayNeedImmediatelyInvokedFunctionExpression = HasAnyDecorators | HasStaticInitializedProperties,
IsExported = IsNamespaceExport | IsDefaultExternalExport | IsNamedExternalExport,
IsExported = IsExportOfNamespace | IsDefaultExternalExport | IsNamedExternalExport,
}
export function transformTypeScript(context: TransformationContext) {
@ -519,14 +520,14 @@ namespace ts {
}
function getClassFacts(node: ClassDeclaration, staticProperties: PropertyDeclaration[]) {
let facts: ClassFacts = 0;
let facts = ClassFacts.None;
if (some(staticProperties)) facts |= ClassFacts.HasStaticInitializedProperties;
if (getClassExtendsHeritageClauseElement(node)) facts |= ClassFacts.HasExtendsClause;
if (shouldEmitDecorateCallForClass(node)) facts |= ClassFacts.HasConstructorDecorators;
if (childIsDecorated(node)) facts |= ClassFacts.HasMemberDecorators;
if (isNamespaceExport(node)) facts |= ClassFacts.IsNamespaceExport;
if ((facts & ClassFacts.IsExported) === 0 && isDefaultExternalModuleExport(node)) facts |= ClassFacts.IsDefaultExternalExport;
if ((facts & ClassFacts.IsExported) === 0 && isNamedExternalModuleExport(node)) facts |= ClassFacts.IsNamedExternalExport;
if (isExportOfNamespace(node)) facts |= ClassFacts.IsExportOfNamespace;
else if (isDefaultExternalModuleExport(node)) facts |= ClassFacts.IsDefaultExternalExport;
else if (isNamedExternalModuleExport(node)) facts |= ClassFacts.IsNamedExternalExport;
if (languageVersion <= ScriptTarget.ES5 && (facts & ClassFacts.MayNeedImmediatelyInvokedFunctionExpression)) facts |= ClassFacts.UseImmediatelyInvokedFunctionExpression;
return facts;
}
@ -609,7 +610,7 @@ namespace ts {
// If the class is exported as part of a TypeScript namespace, emit the namespace export.
// Otherwise, if the class was exported at the top level and was decorated, emit an export
// declaration or export default for the class.
if (facts & ClassFacts.IsNamespaceExport) {
if (facts & ClassFacts.IsExportOfNamespace) {
addExportMemberAssignment(statements, node);
}
else if (facts & ClassFacts.UseImmediatelyInvokedFunctionExpression || facts & ClassFacts.HasConstructorDecorators) {
@ -672,11 +673,6 @@ namespace ts {
/**
* Transforms a decorated class declaration and appends the resulting statements. If
* the class requires an alias to avoid issues with double-binding, the alias is returned.
*
* @param statements A statement list to which to add the declaration.
* @param node A ClassDeclaration node.
* @param name The name of the class.
* @param facts Precomputed facts about the clas.
*/
function createClassDeclarationHeadWithDecorators(node: ClassDeclaration, name: Identifier, facts: ClassFacts) {
// When we emit an ES6 class that has a class decorator, we must tailor the
@ -2223,7 +2219,7 @@ namespace ts {
/*type*/ undefined,
visitFunctionBody(node.body, visitor, context) || createBlock([])
);
if (isNamespaceExport(node)) {
if (isExportOfNamespace(node)) {
const statements: Statement[] = [updated];
addExportMemberAssignment(statements, node);
return statements;
@ -2316,7 +2312,7 @@ namespace ts {
* - The node is exported from a TypeScript namespace.
*/
function visitVariableStatement(node: VariableStatement): Statement {
if (isNamespaceExport(node)) {
if (isExportOfNamespace(node)) {
const variables = getInitializedVariables(node.declarationList);
if (variables.length === 0) {
// elide statement if there are no initialized variables.
@ -2620,7 +2616,7 @@ namespace ts {
* or `exports.x`).
*/
function hasNamespaceQualifiedExportName(node: Node) {
return isNamespaceExport(node)
return isExportOfNamespace(node)
|| (isExternalModuleExport(node)
&& moduleKind !== ModuleKind.ES2015
&& moduleKind !== ModuleKind.System);
@ -3062,7 +3058,7 @@ namespace ts {
const moduleReference = createExpressionFromEntityName(<EntityName>node.moduleReference);
setEmitFlags(moduleReference, EmitFlags.NoComments | EmitFlags.NoNestedComments);
if (isNamedExternalModuleExport(node) || !isNamespaceExport(node)) {
if (isNamedExternalModuleExport(node) || !isExportOfNamespace(node)) {
// export var ${name} = ${moduleReference};
// var ${name} = ${moduleReference};
return setOriginalNode(
@ -3103,7 +3099,7 @@ namespace ts {
*
* @param node The node to test.
*/
function isNamespaceExport(node: Node) {
function isExportOfNamespace(node: Node) {
return currentNamespace !== undefined && hasModifier(node, ModifierFlags.Export);
}

View File

@ -850,72 +850,10 @@ namespace ts {
return node && (node.kind === SyntaxKind.GetAccessor || node.kind === SyntaxKind.SetAccessor);
}
export function isClassLike(node: Node | undefined): node is ClassLikeDeclaration {
export function isClassLike(node: Node): node is ClassLikeDeclaration {
return node && (node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression);
}
export function isImmediatelyInvokedFunctionExpression(node: Node) {
if (!isCallExpression(node) ||
some(node.typeArguments) ||
some(node.arguments)) {
return false;
}
const expression = skipParentheses(node.expression);
if (!isFunctionExpression(expression) ||
some(expression.typeParameters) ||
some(expression.parameters) ||
expression.type ||
!expression.body) {
return false;
}
return true;
}
export function isTypeScriptClassWrapper(node: Node) {
if (!isImmediatelyInvokedFunctionExpression(node) ||
isParseTreeNode(node)) {
return false;
}
const func = <FunctionExpression>skipParentheses((<CallExpression>node).expression);
if (isParseTreeNode(func)) {
return false;
}
const statements = func.body.statements;
if (statements.length < 2) {
return false;
}
const firstStatement = statements[0];
if (isParseTreeNode(firstStatement) ||
!isClassLike(firstStatement) &&
!isClassLikeVariableStatement(firstStatement)) {
return false;
}
const returnStatement = tryCast(elementAt(statements, isVariableStatement(lastOrUndefined(statements)) ? -2 : -1), isReturnStatement);
if (!isReturnStatement(returnStatement) ||
!returnStatement.expression ||
!isIdentifier(skipOuterExpressions(returnStatement.expression))) {
return false;
}
return true;
}
function isClassLikeVariableStatement(node: Node) {
if (!isVariableStatement(node)) return false;
const variable = singleOrUndefined((<VariableStatement>node).declarationList.declarations);
return variable
&& variable.initializer
&& isIdentifier(variable.name)
&& (isClassLike(variable.initializer)
|| (isAssignmentExpression(variable.initializer)
&& isIdentifier(variable.initializer.left)
&& isClassLike(variable.initializer.right)));
}
export function isExpressionStatement(node: Node): node is ExpressionStatement {
return node.kind === SyntaxKind.ExpressionStatement;
}
@ -3438,53 +3376,32 @@ namespace ts {
/**
* Formats an enum value as a string for debugging and debug assertions.
*/
function formatEnum(value = 0, enumObject: any, isFlags: boolean, formatCache?: string[]) {
const cached = formatCache && formatCache[value];
if (cached !== undefined) {
return cached;
}
const result = isFlags ? formatFlagsEnum(value, enumObject) : getEnumName(value, enumObject);
if (formatCache) {
formatCache[value] = result;
}
return result;
}
/**
* Gets the name for an enum value for debugging and debug assertions.
*/
function getEnumName(value: number, enumObject: any) {
for (const name in enumObject) {
if (enumObject[name] === value) {
return name;
}
}
return value.toString();
}
/**
* Formats the bitwise flag values of an enum value for debugging and debug assertions.
*/
function formatFlagsEnum(value: number, enumObject: any) {
function formatEnum(value = 0, enumObject: any, isFlags?: boolean) {
const members = getEnumMembers(enumObject);
let result = "";
if (value === 0) {
return members.length > 0 && members[0][0] === 0 ? members[0][1] : "0";
}
let remainingFlags = value;
for (let i = members.length - 1; i >= 0 && remainingFlags !== 0; i--) {
const [enumValue, enumName] = members[i];
if (enumValue !== 0 && (remainingFlags & enumValue) === enumValue) {
remainingFlags &= ~enumValue;
result = `${enumName}${result ? ", " : ""}${result}`;
if (isFlags) {
let result = "";
let remainingFlags = value;
for (let i = members.length - 1; i >= 0 && remainingFlags !== 0; i--) {
const [enumValue, enumName] = members[i];
if (enumValue !== 0 && (remainingFlags & enumValue) === enumValue) {
remainingFlags &= ~enumValue;
result = `${enumName}${result ? ", " : ""}${result}`;
}
}
if (remainingFlags === 0) {
return result;
}
}
if (remainingFlags === 0) {
return result;
else {
for (const [enumValue, enumName] of members) {
if (enumValue === value) {
return enumName;
}
}
}
return value.toString();
}
@ -3500,39 +3417,32 @@ namespace ts {
return stableSort(result, (x, y) => compareValues(x[0], y[0]));
}
const syntaxKindCache: string[] = [];
export function formatSyntaxKind(kind: SyntaxKind): string {
return formatEnum(kind, (<any>ts).SyntaxKind, /*isFlags*/ false, syntaxKindCache);
return formatEnum(kind, (<any>ts).SyntaxKind, /*isFlags*/ false);
}
const modifierFlagsCache: string[] = [];
export function formatModifierFlags(flags: ModifierFlags): string {
return formatEnum(flags, (<any>ts).ModifierFlags, /*isFlags*/ true, modifierFlagsCache);
return formatEnum(flags, (<any>ts).ModifierFlags, /*isFlags*/ true);
}
const transformFlagsCache: string[] = [];
export function formatTransformFlags(flags: TransformFlags): string {
return formatEnum(flags, (<any>ts).TransformFlags, /*isFlags*/ true, transformFlagsCache);
return formatEnum(flags, (<any>ts).TransformFlags, /*isFlags*/ true);
}
const emitFlagsCache: string[] = [];
export function formatEmitFlags(flags: EmitFlags): string {
return formatEnum(flags, (<any>ts).EmitFlags, /*isFlags*/ true, emitFlagsCache);
return formatEnum(flags, (<any>ts).EmitFlags, /*isFlags*/ true);
}
const symbolFlagsCache: string[] = [];
export function formatSymbolFlags(flags: SymbolFlags): string {
return formatEnum(flags, (<any>ts).SymbolFlags, /*isFlags*/ true, symbolFlagsCache);
return formatEnum(flags, (<any>ts).SymbolFlags, /*isFlags*/ true);
}
const typeFlagsCache: string[] = [];
export function formatTypeFlags(flags: TypeFlags): string {
return formatEnum(flags, (<any>ts).TypeFlags, /*isFlags*/ true, typeFlagsCache);
return formatEnum(flags, (<any>ts).TypeFlags, /*isFlags*/ true);
}
const objectFlagsCache: string[] = [];
export function formatObjectFlags(flags: ObjectFlags): string {
return formatEnum(flags, (<any>ts).ObjectFlags, /*isFlags*/ true, objectFlagsCache);
return formatEnum(flags, (<any>ts).ObjectFlags, /*isFlags*/ true);
}
export function getRangePos(range: TextRange | undefined) {