diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 01d305d93e..5b58d4b872 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -151,8 +151,6 @@ namespace ts { /** * Creates a shallow, memberwise clone of a node for mutation. - * - * @param node The node to clone. */ export function getMutableNode(node: T): T { return cloneNode(node, node, node.flags, node.parent, node); @@ -164,42 +162,28 @@ namespace ts { return node; } - export function createReturn(expression?: Expression): ReturnStatement { - const node = createSynthesizedNode(SyntaxKind.ReturnStatement); - node.expression = expression; - return node; + // Literals + + export function createLiteral(value: string, location?: TextRange): StringLiteral; + export function createLiteral(value: number, location?: TextRange): LiteralExpression; + export function createLiteral(value: string | number | boolean, location?: TextRange): PrimaryExpression; + export function createLiteral(value: string | number | boolean, location?: TextRange): PrimaryExpression { + if (typeof value === "number") { + const node = createNode(SyntaxKind.NumericLiteral, location); + node.text = value.toString(); + return node; + } + else if (typeof value === "boolean") { + return createNode(value ? SyntaxKind.TrueKeyword : SyntaxKind.FalseKeyword, location); + } + else { + const node = createNode(SyntaxKind.StringLiteral, location); + node.text = String(value); + return node; + } } - export function createStatement(expression: Expression): ExpressionStatement { - const node = createSynthesizedNode(SyntaxKind.ExpressionStatement); - node.expression = expression; - return node; - } - - export function createVariableStatement(declarationList: VariableDeclarationList): VariableStatement { - const node = createSynthesizedNode(SyntaxKind.VariableStatement); - node.declarationList = declarationList; - return node; - } - - export function createVariableDeclarationList(declarations: VariableDeclaration[]): VariableDeclarationList { - const node = createSynthesizedNode(SyntaxKind.VariableDeclarationList); - node.declarations = createNodeArray(declarations); - return node; - } - - export function createBlock(statements: Statement[]): Block { - const block = createSynthesizedNode(SyntaxKind.Block); - block.statements = createNodeArray(statements); - return block; - } - - export function createVariableDeclaration(name: BindingPattern | Identifier, initializer?: Expression, location?: TextRange): VariableDeclaration { - const node = createNode(SyntaxKind.VariableDeclaration, location); - node.name = name; - node.initializer = initializer; - return node; - } + // Identifiers export function createIdentifier(text: string): Identifier { const node = createNode(SyntaxKind.Identifier); @@ -207,57 +191,211 @@ namespace ts { return node; } - export function createTempVariable(tempKind: TempVariableKind): Identifier { + export function createTempVariable(): Identifier { const name = createNode(SyntaxKind.Identifier); - name.tempKind = tempKind; + name.text = undefined; + name.tempKind = TempVariableKind.Auto; getNodeId(name); return name; } - export function createLiteral(value: string): StringLiteral; - export function createLiteral(value: number): LiteralExpression; - export function createLiteral(value: string | number | boolean): PrimaryExpression; - export function createLiteral(value: string | number | boolean): PrimaryExpression { - if (typeof value === "number") { - const node = createNode(SyntaxKind.NumericLiteral); - node.text = value.toString(); - return node; - } - else if (typeof value === "boolean") { - return createNode(value ? SyntaxKind.TrueKeyword : SyntaxKind.FalseKeyword); - } - else { - const node = createNode(SyntaxKind.StringLiteral); - node.text = String(value); - return node; - } + export function createLoopVariable(): Identifier { + const name = createNode(SyntaxKind.Identifier); + name.text = undefined; + name.tempKind = TempVariableKind.Loop; + getNodeId(name); + return name; } - export function createVoid(expression: UnaryExpression) { - const node = createNode(SyntaxKind.VoidExpression); + // Reserved words + + export function createSuper() { + const node = createNode(SyntaxKind.SuperKeyword); + return node; + } + + export function createThis(location?: TextRange) { + const node = createNode(SyntaxKind.ThisKeyword, location); + return node; + } + + export function createNull() { + const node = createNode(SyntaxKind.NullKeyword); + return node; + } + + // Names + + export function createComputedPropertyName(expression: Expression, location?: TextRange) { + const node = createNode(SyntaxKind.ComputedPropertyName, location); node.expression = expression; return node; } - export function createVoidZero() { - return createVoid(createLiteral(0)); + // Type members + + export function createMethod(modifiers: Modifier[], name: string | PropertyName, parameters: ParameterDeclaration[], body: Block, location?: TextRange) { + const node = createNode(SyntaxKind.MethodDeclaration, location); + node.decorators = undefined; + setModifiers(node, modifiers); + node.name = typeof name === "string" ? createIdentifier(name) : name; + node.typeParameters = undefined; + node.parameters = createNodeArray(parameters); + node.body = body; + return node; } - export function createPropertyAccess(expression: Expression, name: string | Identifier) { - const node = createNode(SyntaxKind.PropertyAccessExpression); + export function createConstructor(parameters: ParameterDeclaration[], body: Block, location?: TextRange) { + const node = createNode(SyntaxKind.Constructor, location); + node.decorators = undefined; + node.modifiers = undefined; + node.typeParameters = undefined; + node.parameters = createSynthesizedNodeArray(parameters); + node.type = undefined; + node.body = body; + return node; + } + + export function createGetAccessor(modifiers: Modifier[], name: string | PropertyName, body: Block, location?: TextRange) { + const node = createNode(SyntaxKind.GetAccessor, location); + node.decorators = undefined; + setModifiers(node, modifiers); + node.name = typeof name === "string" ? createIdentifier(name) : name; + node.typeParameters = undefined; + node.parameters = createNodeArray(); + node.body = body; + return node; + } + + export function createSetAccessor(modifiers: Modifier[], name: string | PropertyName, parameter: ParameterDeclaration, body: Block, location?: TextRange) { + const node = createNode(SyntaxKind.SetAccessor, location); + node.decorators = undefined; + setModifiers(node, modifiers); + node.name = typeof name === "string" ? createIdentifier(name) : name; + node.typeParameters = undefined; + node.parameters = createNodeArray([parameter]); + node.body = body; + return node; + } + + export function createParameter(name: string | Identifier | BindingPattern, initializer?: Expression, location?: TextRange) { + const node = createNode(SyntaxKind.Parameter, location); + node.decorators = undefined; + node.modifiers = undefined; + node.dotDotDotToken = undefined; + node.name = typeof name === "string" ? createIdentifier(name) : name; + node.questionToken = undefined; + node.type = undefined; + node.initializer = initializer; + return node; + } + + // Expression + + export function createArrayLiteral(elements?: Expression[]) { + const node = createNode(SyntaxKind.ArrayLiteralExpression); + node.elements = createNodeArray(elements); + return node; + } + + export function createObjectLiteral(properties?: ObjectLiteralElement[], location?: TextRange) { + const node = createNode(SyntaxKind.ObjectLiteralExpression, location); + node.properties = createNodeArray(properties); + return node; + } + + export function createPropertyAccess(expression: Expression, name: string | Identifier, location?: TextRange) { + const node = createNode(SyntaxKind.PropertyAccessExpression, location); node.expression = parenthesizeForAccess(expression); node.dotToken = createSynthesizedNode(SyntaxKind.DotToken); node.name = typeof name === "string" ? createIdentifier(name) : name; return node; } - export function createElementAccess(expression: Expression, index: number | Expression) { - const node = createNode(SyntaxKind.ElementAccessExpression); + export function createElementAccess(expression: Expression, index: number | Expression, location?: TextRange) { + const node = createNode(SyntaxKind.ElementAccessExpression, location); node.expression = parenthesizeForAccess(expression); node.argumentExpression = typeof index === "number" ? createLiteral(index) : index; return node; } + export function createCall(expression: Expression, argumentsArray: Expression[], location?: TextRange) { + const node = createNode(SyntaxKind.CallExpression, location); + node.expression = parenthesizeForAccess(expression); + node.arguments = createNodeArray(argumentsArray); + return node; + } + + export function createNew(expression: Expression, argumentsArray: Expression[], location?: TextRange) { + const node = createNode(SyntaxKind.NewExpression, location); + node.expression = parenthesizeForAccess(expression); + node.arguments = argumentsArray ? createNodeArray(argumentsArray) : undefined; + return node; + } + + export function createParen(expression: Expression, location?: TextRange) { + const node = createNode(SyntaxKind.ParenthesizedExpression, location); + node.expression = expression; + return node; + } + + export function createFunctionExpression(asteriskToken: Node, name: string | Identifier, parameters: ParameterDeclaration[], body: Block, location?: TextRange) { + const node = createNode(SyntaxKind.FunctionExpression, location); + node.modifiers = undefined; + node.asteriskToken = asteriskToken; + node.name = typeof name === "string" ? createIdentifier(name) : name; + node.typeParameters = undefined; + node.parameters = createNodeArray(parameters); + node.type = undefined; + node.body = body; + return node; + } + + export function createArrowFunction(parameters: ParameterDeclaration[], body: Expression | Block, location?: TextRange) { + const node = createNode(SyntaxKind.ArrowFunction, location); + node.modifiers = undefined; + node.typeParameters = undefined; + node.parameters = createNodeArray(parameters); + node.type = undefined; + node.equalsGreaterThanToken = createNode(SyntaxKind.EqualsGreaterThanToken); + node.body = body; + return node; + } + + export function createTypeOf(expression: Expression) { + const node = createNode(SyntaxKind.TypeOfExpression); + node.expression = parenthesizePrefixOperand(expression); + return node; + } + + export function createVoid(expression: Expression) { + const node = createNode(SyntaxKind.VoidExpression); + node.expression = parenthesizePrefixOperand(expression); + return node; + } + + export function createPrefix(operator: SyntaxKind, operand: Expression, location?: TextRange) { + const node = createNode(SyntaxKind.PrefixUnaryExpression, location); + node.operator = operator; + node.operand = parenthesizePrefixOperand(operand); + return node; + } + + export function createPostfix(operand: Expression, operator: SyntaxKind, location?: TextRange) { + const node = createNode(SyntaxKind.PostfixUnaryExpression, location); + node.operand = parenthesizePostfixOperand(operand); + node.operator = operator; + return node; + } + + export function createBinary(left: Expression, operator: SyntaxKind, right: Expression, location?: TextRange) { + const node = createNode(SyntaxKind.BinaryExpression, location); + node.left = parenthesizeBinaryOperand(operator, left, /*isLeftSideOfBinary*/ true); + node.operatorToken = createSynthesizedNode(operator); + node.right = parenthesizeBinaryOperand(operator, right, /*isLeftSideOfBinary*/ false); + return node; + } + export function createConditional(condition: Expression, whenTrue: Expression, whenFalse: Expression) { const node = createNode(SyntaxKind.ConditionalExpression); node.condition = condition; @@ -268,14 +406,223 @@ namespace ts { return node; } - export function createBinary(left: Expression, operator: SyntaxKind, right: Expression, location?: TextRange) { - const node = createNode(SyntaxKind.BinaryExpression, location); - node.left = parenthesizeBinaryOperand(operator, left, /*isLeftSideOfBinary*/ true); - node.operatorToken = createSynthesizedNode(operator); - node.right = parenthesizeBinaryOperand(operator, right, /*isLeftSideOfBinary*/ false); + export function createYield(expression: Expression, location?: TextRange) { + const node = createNode(SyntaxKind.YieldExpression, location); + node.expression = expression; return node; } + export function createSpread(expression: Expression) { + const node = createNode(SyntaxKind.SpreadElementExpression); + node.expression = expression; + return node; + } + + export function createClassExpression(name: Identifier, heritageClauses: HeritageClause[], members: ClassElement[], location?: TextRange) { + const node = createNode(SyntaxKind.ClassExpression, location); + node.decorators = undefined; + node.modifiers = undefined; + node.name = name; + node.typeParameters = undefined; + node.heritageClauses = createSynthesizedNodeArray(heritageClauses); + node.members = createSynthesizedNodeArray(members); + return node; + } + + export function createExpressionWithTypeArguments(expression: Expression, location?: TextRange) { + const node = createNode(SyntaxKind.ExpressionWithTypeArguments, location); + node.typeArguments = undefined; + node.expression = parenthesizeForAccess(expression); + return node; + } + + // Element + + export function createBlock(statements: Statement[], location?: TextRange): Block { + const block = createNode(SyntaxKind.Block, location); + block.statements = createNodeArray(statements); + return block; + } + + export function createVariableStatement(modifiers: Modifier[], declarationList: VariableDeclarationList, location?: TextRange): VariableStatement { + const node = createNode(SyntaxKind.VariableStatement, location); + node.decorators = undefined; + setModifiers(node, modifiers); + node.declarationList = declarationList; + return node; + } + + export function createVariableDeclarationList(declarations: VariableDeclaration[], location?: TextRange, flags?: NodeFlags): VariableDeclarationList { + const node = createNode(SyntaxKind.VariableDeclarationList, location, flags); + node.declarations = createNodeArray(declarations); + return node; + } + + export function createLetDeclarationList(declarations: VariableDeclaration[], location?: TextRange) { + return createVariableDeclarationList(declarations, location, NodeFlags.Let); + } + + export function createConstDeclarationList(declarations: VariableDeclaration[], location?: TextRange) { + return createVariableDeclarationList(declarations, location, NodeFlags.Const); + } + + export function createVariableDeclaration(name: string | BindingPattern | Identifier, initializer?: Expression, location?: TextRange): VariableDeclaration { + const node = createNode(SyntaxKind.VariableDeclaration, location); + node.name = typeof name === "string" ? createIdentifier(name) : name; + node.initializer = initializer; + return node; + } + + export function createEmptyStatement(location: TextRange) { + return createNode(SyntaxKind.EmptyStatement, location); + } + + export function createStatement(expression: Expression, location?: TextRange): ExpressionStatement { + const node = createNode(SyntaxKind.ExpressionStatement, location); + node.expression = expression; + return node; + } + + export function createIf(expression: Expression, thenStatement: Statement, elseStatement?: Statement, location?: TextRange) { + const node = createNode(SyntaxKind.IfStatement, location); + node.expression = expression; + node.thenStatement = thenStatement; + node.elseStatement = elseStatement; + return node; + } + + export function createFor(initializer: ForInitializer, condition: Expression, incrementor: Expression, statement: Statement, location?: TextRange) { + const node = createNode(SyntaxKind.ForStatement, location); + node.initializer = initializer; + node.condition = condition; + node.incrementor = incrementor; + node.statement = statement; + return node; + } + + export function createLabel(label: string | Identifier, statement: Statement, location?: TextRange) { + const node = createNode(SyntaxKind.LabeledStatement, location); + node.label = typeof label === "string" ? createIdentifier(label) : label; + node.statement = statement; + return node; + } + + export function createDo(expression: Expression, statement: Statement, location?: TextRange) { + const node = createNode(SyntaxKind.DoStatement, location); + node.expression = expression; + node.statement = statement; + return node; + } + + export function createWhile(statement: Statement, expression: Expression, location?: TextRange) { + const node = createNode(SyntaxKind.WhileStatement, location); + node.statement = statement; + node.expression = expression; + return node; + } + + export function createForIn(initializer: ForInitializer, expression: Expression, statement: Statement, location?: TextRange) { + const node = createNode(SyntaxKind.ForInStatement, location); + node.initializer = initializer; + node.expression = expression; + node.statement = statement; + return node; + } + + export function createForOf(initializer: ForInitializer, expression: Expression, statement: Statement, location?: TextRange) { + const node = createNode(SyntaxKind.ForOfStatement, location); + node.initializer = initializer; + node.expression = expression; + node.statement = statement; + return node; + } + + export function createReturn(expression?: Expression, location?: TextRange): ReturnStatement { + const node = createNode(SyntaxKind.ReturnStatement, location); + node.expression = expression; + return node; + } + + export function createFunctionDeclaration(modifiers: Modifier[], asteriskToken: Node, name: string | Identifier, parameters: ParameterDeclaration[], body: Block, location?: TextRange) { + const node = createNode(SyntaxKind.FunctionDeclaration, location); + node.decorators = undefined; + setModifiers(node, modifiers); + node.asteriskToken = asteriskToken; + node.name = typeof name === "string" ? createIdentifier(name) : name; + node.typeParameters = undefined; + node.parameters = createNodeArray(parameters); + node.type = undefined; + node.body = body; + return node; + } + + export function createClassDeclaration(modifiers: Modifier[], name: Identifier, heritageClauses: HeritageClause[], members: ClassElement[], location?: TextRange) { + const node = createNode(SyntaxKind.ClassDeclaration, location); + node.decorators = undefined; + setModifiers(node, modifiers); + node.name = name; + node.typeParameters = undefined; + node.heritageClauses = createSynthesizedNodeArray(heritageClauses); + node.members = createSynthesizedNodeArray(members); + return node; + } + + export function createExportDefault(expression: Expression) { + const node = createNode(SyntaxKind.ExportAssignment); + node.isExportEquals = false; + node.expression = expression; + return node; + } + + export function createExportDeclaration(exportClause: NamedExports, moduleSpecifier?: Expression) { + const node = createNode(SyntaxKind.ExportDeclaration); + node.exportClause = exportClause; + node.moduleSpecifier = moduleSpecifier; + return node; + } + + export function createNamedExports(elements: ExportSpecifier[]) { + const node = createNode(SyntaxKind.NamedExports); + node.elements = createNodeArray(elements); + return node; + } + + export function createExportSpecifier(name: string | Identifier, propertyName?: string | Identifier) { + const node = createNode(SyntaxKind.ExportSpecifier); + node.name = typeof name === "string" ? createIdentifier(name) : name; + node.propertyName = typeof propertyName === "string" ? createIdentifier(propertyName) : propertyName; + return node; + } + + // Clauses + + export function createHeritageClause(token: SyntaxKind, types: ExpressionWithTypeArguments[], location?: TextRange) { + const node = createNode(SyntaxKind.HeritageClause, location); + node.token = token; + node.types = createSynthesizedNodeArray(types); + return node; + } + + // Property assignments + + export function createPropertyAssignment(name: string | PropertyName, initializer: Expression, location?: TextRange) { + const node = createNode(SyntaxKind.PropertyAssignment, location); + node.name = typeof name === "string" ? createIdentifier(name) : name; + node.questionToken = undefined; + node.initializer = initializer; + return node; + } + + // Compound nodes + + export function createComma(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.CommaToken, right); + } + + export function createLessThan(left: Expression, right: Expression, location?: TextRange) { + return createBinary(left, SyntaxKind.LessThanToken, right, location); + } + export function createAssignment(left: Expression, right: Expression, location?: TextRange) { return createBinary(left, SyntaxKind.EqualsToken, right, location); } @@ -284,17 +631,72 @@ namespace ts { return createBinary(left, SyntaxKind.EqualsEqualsEqualsToken, right); } - export function createComma(left: Expression, right: Expression) { - return createBinary(left, SyntaxKind.CommaToken, right); + export function createStrictInequality(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.ExclamationEqualsEqualsToken, right); } - export function createCall(expression: Expression, argumentsArray: Expression[]) { - const node = createNode(SyntaxKind.CallExpression); - node.expression = parenthesizeForAccess(expression); - node.arguments = createNodeArray(argumentsArray); + export function createAdd(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.PlusToken, right); + } + + export function createSubtract(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.MinusToken, right); + } + + export function createPostfixIncrement(operand: Expression, location?: TextRange) { + return createPostfix(operand, SyntaxKind.PlusPlusToken, location); + } + + export function createLogicalAnd(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.AmpersandAmpersandToken, right); + } + + export function createLogicalOr(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.BarBarToken, right); + } + + export function createLogicalNot(operand: Expression) { + return createPrefix(SyntaxKind.ExclamationToken, operand); + } + + export function createVoidZero() { + return createVoid(createLiteral(0)); + } + + export function createMemberAccessForPropertyName(target: Expression, memberName: PropertyName, location?: TextRange): MemberExpression { + return isIdentifier(memberName) + ? createPropertyAccess(target, cloneNode(memberName), location) + : createElementAccess(target, cloneNode(isComputedPropertyName(memberName) ? memberName.expression : memberName), location); + } + + export function createRestParameter(name: string | Identifier) { + const node = createParameter(name, /*initializer*/ undefined); + node.dotDotDotToken = createSynthesizedNode(SyntaxKind.DotDotDotToken); return node; } + export function createFunctionCall(func: Expression, thisArg: Expression, argumentsList: Expression[], location?: TextRange) { + return createCall( + createPropertyAccess(func, "call"), + [ + thisArg, + ...argumentsList + ], + location + ); + } + + export function createFunctionApply(func: Expression, thisArg: Expression, argumentsExpression: Expression, location?: TextRange) { + return createCall( + createPropertyAccess(func, "apply"), + [ + thisArg, + argumentsExpression + ], + location + ); + } + export function createArraySlice(array: Expression, start?: number | Expression) { const argumentsList: Expression[] = []; if (start !== undefined) { @@ -304,16 +706,353 @@ namespace ts { return createCall(createPropertyAccess(array, "slice"), argumentsList); } - export function parenthesizeExpression(expression: Expression) { - const node = createNode(SyntaxKind.ParenthesizedExpression); - node.expression = expression; - return node; + export function createArrayConcat(array: Expression, values: Expression[]) { + return createCall( + createPropertyAccess(array, "concat"), + values + ); + } + + export function createMathPow(left: Expression, right: Expression, location?: TextRange) { + return createCall( + createPropertyAccess(createIdentifier("Math"), "pow"), + [left, right], + location + ); + } + + export function createJsxSpread(reactNamespace: string, segments: Expression[]) { + return createCall( + createPropertyAccess( + createIdentifier(reactNamespace || "React"), + "__spread" + ), + segments + ); + } + + export function createJsxCreateElement(reactNamespace: string, tagName: Expression, props: Expression, children: Expression[]): LeftHandSideExpression { + const argumentsList = [tagName]; + if (props) { + argumentsList.push(props); + } + + if (children && children.length > 0) { + if (!props) { + argumentsList.push(createNull()); + } + + addRange(argumentsList, children); + } + + return createCall( + createPropertyAccess( + createIdentifier(reactNamespace || "React"), + "createElement" + ), + argumentsList + ); + } + + // Helpers + + export function createExtendsHelper(name: Identifier) { + return createCall( + createIdentifier("__extends"), + [ + name, + createIdentifier("_super") + ] + ); + } + + export function createParamHelper(expression: Expression, parameterOffset: number) { + return createCall( + createIdentifier("__param"), + [ + createLiteral(parameterOffset), + expression + ] + ); + } + + export function createMetadataHelper(metadataKey: string, metadataValue: Expression, defer?: boolean) { + return createCall( + createIdentifier("__metadata"), + [ + createLiteral(metadataKey), + defer + ? createArrowFunction([], metadataValue) + : metadataValue + ] + ); + } + + export function createDecorateHelper(decoratorExpressions: Expression[], target: Expression, memberName?: Expression, descriptor?: Expression) { + const argumentsArray: Expression[] = []; + argumentsArray.push(createArrayLiteral(decoratorExpressions)); + argumentsArray.push(target); + if (memberName) { + argumentsArray.push(memberName); + if (descriptor) { + argumentsArray.push(descriptor); + } + } + + return createCall(createIdentifier("__decorate"), argumentsArray); + } + + export function createAwaiterHelper(hasLexicalArguments: boolean, promiseConstructor: EntityName | Expression, body: Block) { + return createCall( + createIdentifier("__awaiter"), + [ + createThis(), + hasLexicalArguments ? createIdentifier("arguments") : createVoidZero(), + promiseConstructor ? createExpressionFromEntityName(promiseConstructor) : createVoidZero(), + createFunctionExpression( + createNode(SyntaxKind.AsteriskToken), + /*name*/ undefined, + [], + body + ) + ] + ); + } + + export function createHasOwnProperty(target: LeftHandSideExpression, propertyName: Expression) { + return createCall( + createPropertyAccess(target, "hasOwnProperty"), + [propertyName] + ); + } + + function createPropertyDescriptor({ get, set, value, enumerable, configurable, writable }: PropertyDescriptorOptions, preferNewLine?: boolean, location?: TextRange) { + const properties: ObjectLiteralElement[] = []; + addPropertyAssignment(properties, "get", get, preferNewLine); + addPropertyAssignment(properties, "set", set, preferNewLine); + addPropertyAssignment(properties, "value", value, preferNewLine); + addPropertyAssignment(properties, "enumerable", enumerable, preferNewLine); + addPropertyAssignment(properties, "configurable", configurable, preferNewLine); + addPropertyAssignment(properties, "writable", writable, preferNewLine); + return createObjectLiteral(properties, location); + } + + function addPropertyAssignment(properties: ObjectLiteralElement[], name: string, value: boolean | Expression, preferNewLine: boolean) { + if (value !== undefined) { + const property = createPropertyAssignment( + name, + typeof value === "boolean" ? createLiteral(value) : value + ); + + if (preferNewLine) { + property.startsOnNewLine = true; + } + + addNode(properties, property); + } + } + + export interface PropertyDescriptorOptions { + get?: Expression; + set?: Expression; + value?: Expression; + enumerable?: boolean | Expression; + configurable?: boolean | Expression; + writable?: boolean | Expression; + } + + export function createObjectDefineProperty(target: Expression, memberName: Expression, descriptor: PropertyDescriptorOptions, preferNewLine?: boolean, location?: TextRange) { + return createCall( + createPropertyAccess( + createIdentifier("Object"), + "defineProperty" + ), + [ + target, + memberName, + createPropertyDescriptor(descriptor, preferNewLine) + ], + location + ); + } + + function createObjectCreate(prototype: Expression) { + return createCall( + createPropertyAccess(createIdentifier("Object"), "create"), + [prototype] + ); + } + + function createGeti(target: LeftHandSideExpression) { + // name => super[name] + return createArrowFunction( + [createParameter("name")], + createElementAccess( + target, + createIdentifier("name") + ) + ); + } + + function createSeti(target: LeftHandSideExpression) { + // (name, value) => super[name] = value + return createArrowFunction( + [ + createParameter("name"), + createParameter("value") + ], + createAssignment( + createElementAccess( + target, + createIdentifier("name") + ), + createIdentifier("value") + ) + ); + } + + export function createAdvancedAsyncSuperHelper() { + // const _super = (function (geti, seti) { + // const cache = Object.create(null); + // return name => cache[name] || (cache[name] = { get value() { return geti(name); }, set value(v) { seti(name, v); } }); + // })(name => super[name], (name, value) => super[name] = value); + + // const cache = Object.create(null); + const createCache = createVariableStatement( + /*modifiers*/ undefined, + createConstDeclarationList([ + createVariableDeclaration( + "cache", + createObjectCreate(createNull()) + ) + ]) + ); + + // get value() { return geti(name); } + const getter = createGetAccessor( + /*modifiers*/ undefined, + "value", + createBlock([ + createReturn( + createCall( + createIdentifier("geti"), + [createIdentifier("name")] + ) + ) + ]) + ); + + // set value(v) { seti(name, v); } + const setter = createSetAccessor( + /*modifiers*/ undefined, + "value", + createParameter("v"), + createBlock([ + createStatement( + createCall( + createIdentifier("seti"), + [ + createIdentifier("name"), + createIdentifier("v") + ] + ) + ) + ]) + ); + + // return name => cache[name] || ... + const getOrCreateAccessorsForName = createReturn( + createArrowFunction( + [createParameter("name")], + createLogicalOr( + createElementAccess( + createIdentifier("cache"), + createIdentifier("name") + ), + createParen( + createAssignment( + createElementAccess( + createIdentifier("cache"), + createIdentifier("name") + ), + createObjectLiteral([ + getter, + setter + ]) + ) + ) + ) + ) + ); + + // const _super = (function (geti, seti) { + // const cache = Object.create(null); + // return name => cache[name] || (cache[name] = { get value() { return geti(name); }, set value(v) { seti(name, v); } }); + // })(name => super[name], (name, value) => super[name] = value); + return createVariableStatement( + /*modifiers*/ undefined, + createConstDeclarationList([ + createVariableDeclaration( + "_super", + createCall( + createParen( + createFunctionExpression( + /*asteriskToken*/ undefined, + /*name*/ undefined, + [ + createParameter("geti"), + createParameter("seti") + ], + createBlock([ + createCache, + getOrCreateAccessorsForName + ]) + ) + ), + [ + createGeti(createSuper()), + createSeti(createSuper()) + ] + ) + ) + ]) + ); + } + + export function createSimpleAsyncSuperHelper() { + return createVariableStatement( + /*modifiers*/ undefined, + createConstDeclarationList([ + createVariableDeclaration( + "_super", + createGeti(createSuper()) + ) + ]) + ); } export function inlineExpressions(expressions: Expression[]) { return reduceLeft(expressions, createComma); } + export function createExpressionFromEntityName(node: EntityName | Expression): Expression { + return isQualifiedName(node) + ? createPropertyAccess( + createExpressionFromEntityName(node.left), + cloneNode(node.right) + ) + : cloneNode(node); + } + + export function createExpressionForPropertyName(memberName: PropertyName, location?: TextRange): Expression { + return isIdentifier(memberName) ? createLiteral(memberName.text, location) + : isComputedPropertyName(memberName) ? cloneNode(memberName.expression, location) + : cloneNode(memberName, location); + } + + + // Utilities + /** * Wraps the operand to a BinaryExpression in parentheses if they are needed to preserve the intended * order of operations. @@ -323,18 +1062,14 @@ namespace ts { * @param isLeftSideOfBinary A value indicating whether the operand is the left side of the * BinaryExpression. */ - function parenthesizeBinaryOperand(binaryOperator: SyntaxKind, operand: Expression, isLeftSideOfBinary: boolean) { - // When diagnosing whether the expression needs parentheses, the decision should be based - // on the innermost expression in a chain of nested type assertions. - operand = skipAssertions(operand); - + export function parenthesizeBinaryOperand(binaryOperator: SyntaxKind, operand: Expression, isLeftSideOfBinary: boolean) { // If the resulting expression is already parenthesized, we do not need to do any further processing. if (operand.kind === SyntaxKind.ParenthesizedExpression) { return operand; } return binaryOperandNeedsParentheses(binaryOperator, operand, isLeftSideOfBinary) - ? parenthesizeExpression(operand) + ? createParen(operand) : operand; } @@ -441,11 +1176,7 @@ namespace ts { * * @param expr The expression node. */ - function parenthesizeForAccess(expr: Expression): LeftHandSideExpression { - // When diagnosing whether the expression needs parentheses, the decision should be based - // on the innermost expression in a chain of nested type assertions. - expr = skipAssertions(expr); - + export function parenthesizeForAccess(expression: Expression): LeftHandSideExpression { // isLeftHandSideExpression is almost the correct criterion for when it is not necessary // to parenthesize the expression before a dot. The known exceptions are: // @@ -454,25 +1185,112 @@ namespace ts { // NumericLiteral // 1.x -> not the same as (1).x // - if (isLeftHandSideExpression(expr) && - expr.kind !== SyntaxKind.NewExpression && - expr.kind !== SyntaxKind.NumericLiteral) { - return expr; + if (isLeftHandSideExpression(expression) && + expression.kind !== SyntaxKind.NewExpression && + expression.kind !== SyntaxKind.NumericLiteral) { + return expression; } - return parenthesizeExpression(expr); + return createParen(expression, /*location*/ expression); } - /** - * Skips past any TypeAssertionExpression or AsExpression nodes to their inner expression. - * - * @param node The expression node. - */ - function skipAssertions(node: Expression) { - while (node.kind === SyntaxKind.TypeAssertionExpression || node.kind === SyntaxKind.AsExpression) { - node = (node).expression; + export function parenthesizePostfixOperand(operand: Expression) { + return isLeftHandSideExpression(operand) + ? operand + : createParen(operand, /*location*/ operand); + } + + export function parenthesizePrefixOperand(operand: Expression) { + return isUnaryExpression(operand) + ? operand + : createParen(operand, /*location*/ operand); + } + + export function parenthesizeExpressionForList(expression: Expression) { + const expressionPrecedence = getExpressionPrecedence(expression); + const commaPrecedence = getOperatorPrecedence(SyntaxKind.BinaryExpression, SyntaxKind.CommaToken); + return expressionPrecedence > commaPrecedence + ? expression + : createParen(expression, /*location*/ expression); + } + + export function parenthesizeExpressionForExpressionStatement(expression: Expression) { + if (isCallExpression(expression)) { + const callee = expression.expression; + if (callee.kind === SyntaxKind.FunctionExpression + || callee.kind === SyntaxKind.ArrowFunction) { + const clone = cloneNode(expression, expression, expression.flags, expression.parent, expression); + clone.expression = createParen(callee, /*location*/ callee); + return clone; + } + } + else if (getLeftmostExpression(expression).kind === SyntaxKind.ObjectLiteralExpression) { + return createParen(expression, /*location*/ expression); + } + + return expression; + } + + function getLeftmostExpression(node: Expression): Expression { + while (true) { + switch (node.kind) { + case SyntaxKind.PostfixUnaryExpression: + node = (node).operand; + continue; + + case SyntaxKind.BinaryExpression: + node = (node).left; + continue; + + case SyntaxKind.ConditionalExpression: + node = (node).condition; + continue; + + case SyntaxKind.CallExpression: + case SyntaxKind.ElementAccessExpression: + case SyntaxKind.PropertyAccessExpression: + node = (node).expression; + continue; + } + + return node; + } + } + + export function skipParentheses(node: Expression): Expression { + while (node.kind === SyntaxKind.ParenthesizedExpression + || node.kind === SyntaxKind.TypeAssertionExpression + || node.kind === SyntaxKind.AsExpression) { + node = (node).expression; } return node; } + + export function startOnNewLine(node: T): T { + node.startsOnNewLine = true; + return node; + } + + export function setOriginalNode(node: T, original: Node): T { + node.original = original; + return node; + } + + export function setTextRange(node: T, location: TextRange): T { + if (location) { + node.pos = location.pos; + node.end = location.end; + } + return node; + } + + export function setNodeFlags(node: T, flags: NodeFlags): T { + node.flags = flags; + return node; + } + + export function getSynthesizedNode(node: T): T { + return nodeIsSynthesized(node) ? node : cloneNode(node, /*location*/ undefined, node.flags, /*parent*/ undefined, /*original*/ node); + } } \ No newline at end of file diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index b21f9919f4..c4813ac993 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -4069,6 +4069,10 @@ namespace ts { function parseBlock(ignoreMissingOpenBrace: boolean, diagnosticMessage?: DiagnosticMessage): Block { const node = createNode(SyntaxKind.Block); if (parseExpected(SyntaxKind.OpenBraceToken, diagnosticMessage) || ignoreMissingOpenBrace) { + if (scanner.hasPrecedingLineBreak()) { + node.multiLine = true; + } + node.statements = parseList(ParsingContext.BlockStatements, parseStatement); parseExpected(SyntaxKind.CloseBraceToken); } diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index 106dd155c6..45b2ba0ee2 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -42,8 +42,12 @@ namespace ts { hoistFunctionDeclaration, startLexicalEnvironment, endLexicalEnvironment, + identifierSubstitution: node => node, + expressionSubstitution: node => node, enableExpressionSubstitution, isExpressionSubstitutionEnabled, + onBeforeEmitNode: node => { }, + onAfterEmitNode: node => { }, enableEmitNotification, isEmitNotificationEnabled, }; @@ -106,8 +110,9 @@ namespace ts { /** * Sets flags that control emit behavior of a node. */ - function setNodeEmitFlags(node: Node, flags: NodeEmitFlags) { + function setNodeEmitFlags(node: T, flags: NodeEmitFlags) { nodeEmitFlags[getNodeId(node)] = flags; + return node; } /** @@ -196,7 +201,7 @@ namespace ts { case SyntaxKind.ClassExpression: return generateNameForClassExpression(); default: - return createTempVariable(TempVariableKind.Auto); + return createTempVariable(); } } @@ -276,6 +281,7 @@ namespace ts { if (hoistedVariableDeclarations) { const statement = createVariableStatement( + /*modifiers*/ undefined, createVariableDeclarationList(hoistedVariableDeclarations) ); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 6d7a743c56..1422b61165 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1125,6 +1125,7 @@ namespace ts { // @kind(SyntaxKind.Block) export interface Block extends Statement { statements: NodeArray; + /*@internal*/ multiLine?: boolean; } // @kind(SyntaxKind.VariableStatement) @@ -1302,7 +1303,7 @@ namespace ts { export interface EnumMember extends Declaration { // This does include ComputedPropertyName, but the parser will give an error // if it parses a ComputedPropertyName in an EnumMember - name: DeclarationName; + name: PropertyName; initializer?: Expression; } @@ -2799,8 +2800,7 @@ namespace ts { UMDDefine = 1 << 4, // This node should be replaced with the UMD define helper. NoLexicalEnvironment = 1 << 5, // A new LexicalEnvironment should *not* be introduced when emitting this node, this is primarily used when printing a SystemJS module. SingleLine = 1 << 6, // The contents of this node should be emit on a single line. - MultiLine = 1 << 7, // The contents of this node should be emit on multiple lines. - AdviseOnEmitNode = 1 << 8, // The node printer should invoke the onBeforeEmitNode and onAfterEmitNode callbacks when printing this node. + AdviseOnEmitNode = 1 << 7, // The node printer should invoke the onBeforeEmitNode and onAfterEmitNode callbacks when printing this node. } /** Additional context provided to `visitEachChild` */ @@ -2817,7 +2817,7 @@ namespace ts { getCompilerOptions(): CompilerOptions; getEmitResolver(): EmitResolver; getNodeEmitFlags(node: Node): NodeEmitFlags; - setNodeEmitFlags(node: Node, flags: NodeEmitFlags): void; + setNodeEmitFlags(node: T, flags: NodeEmitFlags): T; hoistFunctionDeclaration(node: FunctionDeclaration): void; hoistVariableDeclaration(node: Identifier): void; isUniqueName(name: string): boolean; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 1951258130..e20ba3ac4d 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -173,6 +173,10 @@ namespace ts { return node.pos; } + export function isDefined(value: any): boolean { + return value !== undefined; + } + // Returns true if this node is missing from the actual source code. A 'missing' node is different // from 'undefined/defined'. When a node is undefined (which can happen for optional nodes // in the tree), it is definitely missing. However, a node may be defined, but still be @@ -1654,7 +1658,7 @@ namespace ts { return getOperatorPrecedence(expression.kind, operator, hasArguments); } - function getOperator(expression: Expression) { + export function getOperator(expression: Expression) { if (expression.kind === SyntaxKind.BinaryExpression) { return (expression).operatorToken.kind; } @@ -2664,6 +2668,10 @@ namespace ts { // Literals + export function isNoSubstitutionTemplateLiteral(node: Node): node is LiteralExpression { + return node.kind === SyntaxKind.NoSubstitutionTemplateLiteral; + } + export function isLiteralKind(kind: SyntaxKind): boolean { return SyntaxKind.FirstLiteralToken <= kind && kind <= SyntaxKind.LastLiteralToken; } @@ -2757,6 +2765,10 @@ namespace ts { // Type members + export function isMethodDeclaration(node: Node): node is MethodDeclaration { + return node.kind === SyntaxKind.MethodDeclaration; + } + export function isClassElement(node: Node): node is ClassElement { const kind = node.kind; return kind === SyntaxKind.Constructor @@ -2764,7 +2776,6 @@ namespace ts { || kind === SyntaxKind.MethodDeclaration || kind === SyntaxKind.GetAccessor || kind === SyntaxKind.SetAccessor - || kind === SyntaxKind.MethodSignature || kind === SyntaxKind.IndexSignature; } @@ -2830,6 +2841,10 @@ namespace ts { return node.kind === SyntaxKind.BinaryExpression; } + export function isConditionalExpression(node: Node): node is ConditionalExpression { + return node.kind === SyntaxKind.ConditionalExpression; + } + export function isCallExpression(node: Node): node is CallExpression { return node.kind === SyntaxKind.CallExpression; } @@ -2840,11 +2855,16 @@ namespace ts { || kind === SyntaxKind.NoSubstitutionTemplateLiteral; } + export function isSpreadElementExpression(node: Node): node is SpreadElementExpression { + return node.kind === SyntaxKind.SpreadElementExpression; + } + export function isExpressionWithTypeArguments(node: Node): node is ExpressionWithTypeArguments { return node.kind === SyntaxKind.ExpressionWithTypeArguments; } - function isLeftHandSideExpressionKind(kind: SyntaxKind) { + export function isLeftHandSideExpression(node: Node): node is LeftHandSideExpression { + const kind = node.kind; return kind === SyntaxKind.PropertyAccessExpression || kind === SyntaxKind.ElementAccessExpression || kind === SyntaxKind.NewExpression @@ -2870,11 +2890,8 @@ namespace ts { || kind === SyntaxKind.SuperKeyword; } - export function isLeftHandSideExpression(node: Node): node is LeftHandSideExpression { - return isLeftHandSideExpressionKind(node.kind); - } - - function isUnaryExpressionKind(kind: SyntaxKind): boolean { + export function isUnaryExpression(node: Node): node is UnaryExpression { + const kind = node.kind; return kind === SyntaxKind.PrefixUnaryExpression || kind === SyntaxKind.PostfixUnaryExpression || kind === SyntaxKind.DeleteExpression @@ -2882,14 +2899,11 @@ namespace ts { || kind === SyntaxKind.VoidExpression || kind === SyntaxKind.AwaitExpression || kind === SyntaxKind.TypeAssertionExpression - || isLeftHandSideExpressionKind(kind); + || isLeftHandSideExpression(node); } - export function isUnaryExpression(node: Node): node is UnaryExpression { - return isUnaryExpressionKind(node.kind); - } - - function isExpressionKind(kind: SyntaxKind): boolean { + export function isExpression(node: Node): node is Expression { + const kind = node.kind; return kind === SyntaxKind.ConditionalExpression || kind === SyntaxKind.YieldExpression || kind === SyntaxKind.ArrowFunction @@ -2897,11 +2911,7 @@ namespace ts { || kind === SyntaxKind.SpreadElementExpression || kind === SyntaxKind.AsExpression || kind === SyntaxKind.OmittedExpression - || isUnaryExpressionKind(kind); - } - - export function isExpression(node: Node): node is Expression { - return isExpressionKind(node.kind); + || isUnaryExpression(node); } // Misc @@ -3053,7 +3063,8 @@ namespace ts { export function isStatement(node: Node): node is Statement { const kind = node.kind; return isStatementKindButNotDeclarationKind(kind) - || isDeclarationStatementKind(kind); + || isDeclarationStatementKind(kind) + || kind === SyntaxKind.Block; } // Module references @@ -3089,6 +3100,10 @@ namespace ts { || kind === SyntaxKind.JsxSpreadAttribute; } + export function isJsxSpreadAttribute(node: Node): node is JsxSpreadAttribute { + return node.kind === SyntaxKind.JsxSpreadAttribute; + } + // Clauses export function isCaseOrDefaultClause(node: Node): node is CaseOrDefaultClause { @@ -3108,9 +3123,12 @@ namespace ts { // Property assignments - export function isShortHandPropertyAssignment(node: Node): node is ShorthandPropertyAssignment { - const kind = node.kind; - return kind === SyntaxKind.ShorthandPropertyAssignment; + export function isPropertyAssignment(node: Node): node is PropertyAssignment { + return node.kind === SyntaxKind.PropertyAssignment; + } + + export function isShorthandPropertyAssignment(node: Node): node is ShorthandPropertyAssignment { + return node.kind === SyntaxKind.ShorthandPropertyAssignment; } // Enum diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index f49e3ef6aa..be5e5fd21c 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -18,6 +18,9 @@ namespace ts { /** A callback used to lift a NodeArrayNode into a valid node. */ lift?: (nodes: NodeArray) => Node; + + /** A callback used to parenthesize a node to preserve the intended order of operations. */ + parenthesize?: (value: Node, parentNode: Node) => Node; }; /** @@ -51,7 +54,7 @@ namespace ts { { name: "modifiers", test: isModifier }, { name: "name", test: isBindingName }, { name: "type", test: isTypeNode, optional: true }, - { name: "initializer", test: isExpression, optional: true }, + { name: "initializer", test: isExpression, optional: true, parenthesize: parenthesizeExpressionForList }, ], [SyntaxKind.Decorator]: [ { name: "expression", test: isLeftHandSideExpression }, @@ -107,34 +110,34 @@ namespace ts { [SyntaxKind.BindingElement]: [ { name: "propertyName", test: isPropertyName, optional: true }, { name: "name", test: isBindingName }, - { name: "initializer", test: isExpression, optional: true }, + { name: "initializer", test: isExpression, optional: true, parenthesize: parenthesizeExpressionForList }, ], [SyntaxKind.ArrayLiteralExpression]: [ - { name: "elements", test: isExpression }, + { name: "elements", test: isExpression, parenthesize: parenthesizeExpressionForList }, ], [SyntaxKind.ObjectLiteralExpression]: [ { name: "properties", test: isObjectLiteralElement }, ], [SyntaxKind.PropertyAccessExpression]: [ - { name: "expression", test: isLeftHandSideExpression }, + { name: "expression", test: isLeftHandSideExpression, parenthesize: parenthesizeForAccess }, { name: "name", test: isIdentifier }, ], [SyntaxKind.ElementAccessExpression]: [ - { name: "expression", test: isLeftHandSideExpression }, + { name: "expression", test: isLeftHandSideExpression, parenthesize: parenthesizeForAccess }, { name: "argumentExpression", test: isExpression }, ], [SyntaxKind.CallExpression]: [ - { name: "expression", test: isLeftHandSideExpression }, + { name: "expression", test: isLeftHandSideExpression, parenthesize: parenthesizeForAccess }, { name: "typeArguments", test: isTypeNode }, { name: "arguments", test: isExpression }, ], [SyntaxKind.NewExpression]: [ - { name: "expression", test: isLeftHandSideExpression }, + { name: "expression", test: isLeftHandSideExpression, parenthesize: parenthesizeForAccess }, { name: "typeArguments", test: isTypeNode }, { name: "arguments", test: isExpression }, ], [SyntaxKind.TaggedTemplateExpression]: [ - { name: "tag", test: isLeftHandSideExpression }, + { name: "tag", test: isLeftHandSideExpression, parenthesize: parenthesizeForAccess }, { name: "template", test: isTemplate }, ], [SyntaxKind.TypeAssertionExpression]: [ @@ -162,26 +165,26 @@ namespace ts { { name: "body", test: isConciseBody, lift: liftToBlock }, ], [SyntaxKind.DeleteExpression]: [ - { name: "expression", test: isUnaryExpression }, + { name: "expression", test: isUnaryExpression, parenthesize: parenthesizePrefixOperand }, ], [SyntaxKind.TypeOfExpression]: [ - { name: "expression", test: isUnaryExpression }, + { name: "expression", test: isUnaryExpression, parenthesize: parenthesizePrefixOperand }, ], [SyntaxKind.VoidExpression]: [ - { name: "expression", test: isUnaryExpression }, + { name: "expression", test: isUnaryExpression, parenthesize: parenthesizePrefixOperand }, ], [SyntaxKind.AwaitExpression]: [ - { name: "expression", test: isUnaryExpression }, + { name: "expression", test: isUnaryExpression, parenthesize: parenthesizePrefixOperand }, ], [SyntaxKind.PrefixUnaryExpression]: [ - { name: "operand", test: isUnaryExpression }, + { name: "operand", test: isUnaryExpression, parenthesize: parenthesizePrefixOperand }, ], [SyntaxKind.PostfixUnaryExpression]: [ - { name: "operand", test: isLeftHandSideExpression }, + { name: "operand", test: isLeftHandSideExpression, parenthesize: parenthesizePostfixOperand }, ], [SyntaxKind.BinaryExpression]: [ - { name: "left", test: isExpression }, - { name: "right", test: isExpression }, + { name: "left", test: isExpression, parenthesize: (node: Expression, parent: BinaryExpression) => parenthesizeBinaryOperand(getOperator(parent), node, true) }, + { name: "right", test: isExpression, parenthesize: (node: Expression, parent: BinaryExpression) => parenthesizeBinaryOperand(getOperator(parent), node, false) }, ], [SyntaxKind.ConditionalExpression]: [ { name: "condition", test: isExpression }, @@ -196,7 +199,7 @@ namespace ts { { name: "expression", test: isExpression, optional: true }, ], [SyntaxKind.SpreadElementExpression]: [ - { name: "expression", test: isExpression }, + { name: "expression", test: isExpression, parenthesize: parenthesizeExpressionForList }, ], [SyntaxKind.ClassExpression]: [ { name: "decorators", test: isDecorator }, @@ -207,7 +210,7 @@ namespace ts { { name: "members", test: isClassElement }, ], [SyntaxKind.ExpressionWithTypeArguments]: [ - { name: "expression", test: isLeftHandSideExpression }, + { name: "expression", test: isLeftHandSideExpression, parenthesize: parenthesizeForAccess }, { name: "typeArguments", test: isTypeNode }, ], [SyntaxKind.AsExpression]: [ @@ -227,7 +230,7 @@ namespace ts { { name: "declarationList", test: isVariableDeclarationList }, ], [SyntaxKind.ExpressionStatement]: [ - { name: "expression", test: isExpression }, + { name: "expression", test: isExpression, parenthesize: parenthesizeExpressionForExpressionStatement }, ], [SyntaxKind.IfStatement]: [ { name: "expression", test: isExpression }, @@ -290,7 +293,7 @@ namespace ts { [SyntaxKind.VariableDeclaration]: [ { name: "name", test: isBindingName }, { name: "type", test: isTypeNode, optional: true }, - { name: "initializer", test: isExpression, optional: true }, + { name: "initializer", test: isExpression, optional: true, parenthesize: parenthesizeExpressionForList }, ], [SyntaxKind.VariableDeclarationList]: [ { name: "declarations", test: isVariableDeclaration }, @@ -419,7 +422,7 @@ namespace ts { ], [SyntaxKind.PropertyAssignment]: [ { name: "name", test: isPropertyName }, - { name: "initializer", test: isExpression }, + { name: "initializer", test: isExpression, parenthesize: parenthesizeExpressionForList }, ], [SyntaxKind.ShorthandPropertyAssignment]: [ { name: "name", test: isIdentifier }, @@ -427,7 +430,7 @@ namespace ts { ], [SyntaxKind.EnumMember]: [ { name: "name", test: isPropertyName }, - { name: "initializer", test: isExpression, optional: true }, + { name: "initializer", test: isExpression, optional: true, parenthesize: parenthesizeExpressionForList }, ], [SyntaxKind.SourceFile]: [ { name: "statements", test: isStatement }, @@ -622,13 +625,6 @@ namespace ts { return updated; } - /** - * Sets the value of an edge, adjusting the value as necessary for cases such as expression precedence. - */ - function setEdgeValue(parentNode: Node & Map, edge: NodeEdge, value: Node | NodeArray) { - parentNode[edge.name] = value; - } - /** * Visits a node edge. * @@ -642,6 +638,26 @@ namespace ts { : visitNode(value, visitor, edge.test, edge.optional, edge.lift); } + /** + * Sets the value of an edge, adjusting the value as necessary for cases such as expression precedence. + */ + function setEdgeValue(parentNode: Node & Map, edge: NodeEdge, value: Node | NodeArray) { + if (value && edge.parenthesize && !isArray(value)) { + value = parenthesizeEdge(value, parentNode, edge.parenthesize, edge.test); + } + + parentNode[edge.name] = value; + } + + /** + * Applies parentheses to a node to ensure the correct precedence. + */ + function parenthesizeEdge(node: Node, parentNode: Node, parenthesize: (node: Node, parentNode: Node) => Node, test: (node: Node) => boolean) { + node = parenthesize(node, parentNode); + Debug.assert(test === undefined || test(node), "Unexpected node kind after visit."); + return node; + } + /** * Appends a node to an array. * @@ -848,7 +864,7 @@ namespace ts { * * @param nodes The NodeArray. */ - function liftToBlock(nodes: NodeArray) { + export function liftToBlock(nodes: NodeArray) { Debug.assert(every(nodes, isStatement), "Cannot lift nodes to a Block."); return createBlock(>nodes); }