diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index c28162d8723..754dea2905c 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -1,11 +1,12 @@ /// /// +/* @internal */ namespace ts { let NodeConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; let SourceFileConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; - export function createNode(kind: SyntaxKind, pos?: number, end?: number): Node { + function createNode(kind: SyntaxKind, pos?: number, end?: number): Node { if (kind === SyntaxKind.SourceFile) { return new (SourceFileConstructor || (SourceFileConstructor = objectAllocator.getSourceFileConstructor()))(kind, pos, end); } @@ -14,7 +15,10 @@ namespace ts { } } - /* @internal */ + function allocateNode(kind: SyntaxKind, location?: TextRange) { + return location ? createNode(kind, location.pos, location.end) : createSynthesizedNode(kind); + } + export function createNodeArray(elements?: T[], pos?: number, end?: number): NodeArray { const array = >(elements || []); array.pos = pos; @@ -23,7 +27,6 @@ namespace ts { return array; } - /* @internal */ export function createModifiersArray(elements?: Modifier[], pos?: number, end?: number): ModifiersArray { const array = (elements || []); array.pos = pos; @@ -33,19 +36,16 @@ namespace ts { return array; } - /* @internal */ export function createSynthesizedNode(kind: SyntaxKind, startsOnNewLine?: boolean): Node { const node = createNode(kind, /*pos*/ -1, /*end*/ -1); node.startsOnNewLine = startsOnNewLine; return node; } - /* @internal */ export function createSynthesizedNodeArray(elements?: T[]): NodeArray { return createNodeArray(elements, /*pos*/ -1, /*end*/ -1); } - /* @internal */ export function createSynthesizedModifiersArray(elements?: Modifier[]): ModifiersArray { return createModifiersArray(elements, /*pos*/ -1, /*end*/ -1); } @@ -61,7 +61,6 @@ namespace ts { * @param parent The parent for the new node. * @param original An optional pointer to the original source tree node. */ - /* @internal */ export function cloneNode(node: T, location?: TextRange, flags?: NodeFlags, parent?: Node, original?: Node): T { // We don't use "clone" from core.ts here, as we need to preserve the prototype chain of // the original node. We also need to exclude specific properties and only include own- @@ -93,46 +92,252 @@ namespace ts { return clone; } - /* @internal */ export function createNodeArrayNode(elements: T[]): NodeArrayNode { const node = >createSynthesizedNode(SyntaxKind.NodeArrayNode); node.nodes = createNodeArray(elements); return node; } - /* @internal */ export function createReturn(expression?: Expression): ReturnStatement { const node = createSynthesizedNode(SyntaxKind.ReturnStatement); node.expression = expression; return node; } - /* @internal */ export function createStatement(expression: Expression): ExpressionStatement { const node = createSynthesizedNode(SyntaxKind.ExpressionStatement); node.expression = expression; return node; } - /* @internal */ export function createVariableStatement(declarationList: VariableDeclarationList): VariableStatement { const node = createSynthesizedNode(SyntaxKind.VariableStatement); node.declarationList = declarationList; return node; } - /* @internal */ export function createVariableDeclarationList(declarations: VariableDeclaration[]): VariableDeclarationList { const node = createSynthesizedNode(SyntaxKind.VariableDeclarationList); node.declarations = createNodeArray(declarations); return node; } - /* @internal */ 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 = allocateNode(SyntaxKind.VariableDeclaration, location); + node.name = name; + node.initializer = initializer; + return node; + } + + export function createIdentifier(text: string): Identifier { + const node = allocateNode(SyntaxKind.Identifier); + node.text = text; + return node; + } + + export function createTempVariable(tempKind: TempVariableKind): Identifier { + const name = allocateNode(SyntaxKind.Identifier); + name.tempKind = tempKind; + getNodeId(name); + return name; + } + + export function createLiteral(value: string): StringLiteral; + export function createLiteral(value: number): LiteralExpression; + export function createLiteral(value: string | number | boolean | void): PrimaryExpression; + export function createLiteral(value: string | number | boolean | void): T { + if (typeof value === "string") { + const node = allocateNode(SyntaxKind.StringLiteral); + node.text = value; + return node; + } + else if (typeof value === "number") { + const node = allocateNode(SyntaxKind.NumericLiteral); + node.text = value.toString(); + return node; + } + else if (typeof value === "boolean") { + return allocateNode(value ? SyntaxKind.TrueKeyword : SyntaxKind.FalseKeyword); + } + else if (value === null) { + return allocateNode(SyntaxKind.NullKeyword); + } + } + + export function createVoid(expression: UnaryExpression) { + const node = allocateNode(SyntaxKind.VoidExpression); + node.expression = expression; + return node; + } + + export function createVoidZero() { + return createVoid(createLiteral(0)); + } + + export function createPropertyAccess(expression: Expression, name: string | Identifier) { + const node = allocateNode(SyntaxKind.PropertyAccessExpression); + node.expression = parenthesizeForAccess(expression); + node.dotToken = createSynthesizedNode(SyntaxKind.DotToken); + node.name = coerceIdentifier(name); + return node; + } + + export function createElementAccess(expression: Expression, index: string | number | Expression) { + const node = allocateNode(SyntaxKind.ElementAccessExpression); + node.expression = parenthesizeForAccess(expression); + node.argumentExpression = coerceExpression(index); + return node; + } + + export function createConditional(condition: Expression, whenTrue: Expression, whenFalse: Expression) { + const node = allocateNode(SyntaxKind.ConditionalExpression); + node.condition = condition; + node.questionToken = createSynthesizedNode(SyntaxKind.QualifiedName); + node.whenTrue = whenTrue; + node.colonToken = createSynthesizedNode(SyntaxKind.ColonToken); + node.whenFalse = whenFalse; + return node; + } + + export function createBinary(left: Expression, operator: SyntaxKind, right: Expression, location?: TextRange) { + const node = allocateNode(SyntaxKind.BinaryExpression, location); + node.left = parenthesizeForBinary(left, operator, BinaryOperand.Left); + node.operatorToken = createSynthesizedNode(operator); + node.right = parenthesizeForBinary(right, operator, BinaryOperand.Right); + return node; + } + + export function createAssignment(left: Expression, right: Expression, location?: TextRange) { + return createBinary(left, SyntaxKind.EqualsToken, right, location); + } + + export function createStrictEquality(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.EqualsEqualsEqualsToken, right); + } + + export function createComma(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.CommaToken, right); + } + + export function createCall(expression: Expression, argumentsArray: Expression[]) { + const node = allocateNode(SyntaxKind.CallExpression); + node.expression = parenthesizeForAccess(expression); + node.arguments = createNodeArray(argumentsArray); + return node; + } + + export function createArraySlice(array: Expression, start?: number | Expression) { + const argumentsList: Expression[] = start !== undefined ? [coerceExpression(start)] : []; + return createCall(createPropertyAccess(array, "slice"), argumentsList); + } + + export function parenthesizeExpression(expression: Expression) { + const node = allocateNode(SyntaxKind.ParenthesizedExpression); + node.expression = expression; + return node; + } + + export function inlineExpressions(expressions: Expression[]) { + return reduceLeft(expressions, createComma); + } + + function coerceIdentifier(value: string | Identifier) { + if (typeof value === "string") { + return createIdentifier(value); + } + else { + return value; + } + } + + function coerceExpression(value: string | number | boolean | Expression): Expression { + if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") { + return createLiteral(value); + } + else if (value === null) { + return createLiteral(null); + } + else { + return value; + } + } + + const enum BinaryOperand { + Left, + Right + } + + function parenthesizeForBinary(operand: Expression, operator: SyntaxKind, side: BinaryOperand) { + // When diagnosing whether the expression needs parentheses, the decision should be based + // on the innermost expression in a chain of nested type assertions. + while (operand.kind === SyntaxKind.TypeAssertionExpression || operand.kind === SyntaxKind.AsExpression) { + operand = (operand).expression; + } + + // If the resulting expression is already parenthesized, we do not need to do any further processing. + if (operand.kind === SyntaxKind.ParenthesizedExpression) { + return operand; + } + + return needsParenthesesForBinary(operand, operator, side) + ? parenthesizeExpression(operand) + : operand; + } + + function needsParenthesesForBinary(operand: Expression, operator: SyntaxKind, side: BinaryOperand) { + const operandPrecedence = getExpressionPrecedence(operand); + const operatorPrecedence = getOperatorPrecedence(SyntaxKind.BinaryExpression, operator); + switch (compareValues(operandPrecedence, operatorPrecedence)) { + case Comparison.LessThan: + return true; + case Comparison.EqualTo: + return isRightAssociativeOperandOnLeftHandSide(operand, side) + || isModuloOperandOnRightHandSide(operand, operator, side); + case Comparison.GreaterThan: + return false; + } + } + + function isRightAssociativeOperandOnLeftHandSide(operand: Expression, side: BinaryOperand) { + return side === BinaryOperand.Left + && getExpressionAssociativity(operand) === Associativity.Right; + } + + function isModuloOperandOnRightHandSide(operand: Expression, operator: SyntaxKind, side: BinaryOperand) { + return side === BinaryOperand.Right + && operator !== SyntaxKind.PercentToken + && operand.kind === SyntaxKind.BinaryExpression + && (operand).operatorToken.kind === SyntaxKind.PercentToken; + } + + 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. + while (expr.kind === SyntaxKind.TypeAssertionExpression || expr.kind === SyntaxKind.AsExpression) { + expr = (expr).expression; + } + + // isLeftHandSideExpression is almost the correct criterion for when it is not necessary + // to parenthesize the expression before a dot. The known exceptions are: + // + // NewExpression: + // new C.x -> not the same as (new C).x + // NumberLiteral + // 1.x -> not the same as (1).x + // + if (isLeftHandSideExpression(expr) && + expr.kind !== SyntaxKind.NewExpression && + expr.kind !== SyntaxKind.NumericLiteral) { + + return expr; + } + + return parenthesizeExpression(expr); + } } \ No newline at end of file diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 1d9f9fe2305..b21f9919f45 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -5,6 +5,18 @@ namespace ts { /* @internal */ export let parseTime = 0; + let NodeConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; + let SourceFileConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; + + export function createNode(kind: SyntaxKind, pos?: number, end?: number): Node { + if (kind === SyntaxKind.SourceFile) { + return new (SourceFileConstructor || (SourceFileConstructor = objectAllocator.getSourceFileConstructor()))(kind, pos, end); + } + else { + return new (NodeConstructor || (NodeConstructor = objectAllocator.getNodeConstructor()))(kind, pos, end); + } + } + function visitNode(cbNode: (node: Node) => T, node: Node): T { if (node) { return cbNode(node); diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts new file mode 100644 index 00000000000..a4a5ae7c91a --- /dev/null +++ b/src/compiler/transformer.ts @@ -0,0 +1,331 @@ +/// + +/* @internal */ +namespace ts { + export function getTransformers(compilerOptions: CompilerOptions) { + const transformers: Transformer[] = []; + // TODO(rbuckton): Add transformers + return transformers; + } + + export function transformFiles(resolver: EmitResolver, host: EmitHost, sourceFiles: SourceFile[], transformers: Transformer[]) { + const nodeToGeneratedName: Identifier[] = []; + const generatedNameSet: Map = {}; + const nodeEmitFlags: NodeEmitFlags[] = []; + const lexicalEnvironmentVariableDeclarationsStack: VariableDeclaration[][] = []; + const lexicalEnvironmentFunctionDeclarationsStack: FunctionDeclaration[][] = []; + let lexicalEnvironmentStackOffset = 0; + let hoistedVariableDeclarations: VariableDeclaration[]; + let hoistedFunctionDeclarations: FunctionDeclaration[]; + let currentSourceFile: SourceFile; + + const context: TransformationContext = { + getCompilerOptions: () => host.getCompilerOptions(), + getEmitResolver: () => resolver, + getNodeEmitFlags, + setNodeEmitFlags, + isUniqueName, + getGeneratedNameForNode, + nodeHasGeneratedName, + makeUniqueName, + hoistVariableDeclaration, + hoistFunctionDeclaration, + startLexicalEnvironment, + endLexicalEnvironment + }; + + // Chain together and initialize each transformer. + const transformation = chain(...transformers)(context); + + // Transform each source file. + return map(sourceFiles, transformSourceFile); + + /** + * Transforms a source file. + * @param sourceFile The source file to transform. + */ + function transformSourceFile(sourceFile: SourceFile) { + if (isDeclarationFile(sourceFile)) { + return sourceFile; + } + + currentSourceFile = sourceFile; + const visited = transformation(sourceFile); + currentSourceFile = undefined; + return visited; + } + + /** + * Gets flags that control emit behavior of a node. + */ + function getNodeEmitFlags(node: Node) { + while (node) { + const nodeId = getNodeId(node); + if (nodeEmitFlags[nodeId] !== undefined) { + return nodeEmitFlags[nodeId]; + } + + node = node.original; + } + + return undefined; + } + + /** + * Sets flags that control emit behavior of a node. + */ + function setNodeEmitFlags(node: Node, flags: NodeEmitFlags) { + nodeEmitFlags[getNodeId(node)] = flags; + } + + /** + * Generate a name that is unique within the current file and doesn't conflict with any names + * in global scope. The name is formed by adding an '_n' suffix to the specified base name, + * where n is a positive integer. Note that names generated by makeTempVariableName and + * makeUniqueName are guaranteed to never conflict. + */ + function makeUniqueName(baseName: string): Identifier { + // Find the first unique 'name_n', where n is a positive number + if (baseName.charCodeAt(baseName.length - 1) !== CharacterCodes._) { + baseName += "_"; + } + + let i = 1; + while (true) { + const generatedName = baseName + i; + if (isUniqueName(generatedName)) { + return createIdentifier(generatedNameSet[generatedName] = generatedName); + } + + i++; + } + } + + /** + * Gets the generated name for a node. + */ + function getGeneratedNameForNode(node: Node) { + const id = getNodeId(node); + return nodeToGeneratedName[id] || (nodeToGeneratedName[id] = generateNameForNode(node)); + } + + /** + * Gets a value indicating whether a node has a generated name. + */ + function nodeHasGeneratedName(node: Node) { + const id = getNodeId(node); + return nodeToGeneratedName[id] !== undefined; + } + + /** + * Tests whether the provided name is unique. + */ + function isUniqueName(name: string): boolean { + return !resolver.hasGlobalName(name) + && !hasProperty(currentSourceFile.identifiers, name) + && !hasProperty(generatedNameSet, name); + } + + /** + * Tests whether the provided name is unique within a container. + */ + function isUniqueLocalName(name: string, container: Node): boolean { + container = getOriginalNode(container); + for (let node = container; isNodeDescendentOf(node, container); node = node.nextContainer) { + if (node.locals && hasProperty(node.locals, name)) { + // We conservatively include alias symbols to cover cases where they're emitted as locals + if (node.locals[name].flags & (SymbolFlags.Value | SymbolFlags.ExportValue | SymbolFlags.Alias)) { + return false; + } + } + } + return true; + } + + /** + * Generates a name for a node. + */ + function generateNameForNode(node: Node): Identifier { + switch (node.kind) { + case SyntaxKind.Identifier: + return makeUniqueName((node).text); + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.EnumDeclaration: + return generateNameForModuleOrEnum(node); + case SyntaxKind.ImportDeclaration: + case SyntaxKind.ExportDeclaration: + return generateNameForImportOrExportDeclaration(node); + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ExportAssignment: + return generateNameForExportDefault(); + case SyntaxKind.ClassExpression: + return generateNameForClassExpression(); + case SyntaxKind.ComputedPropertyName: + case SyntaxKind.Parameter: + case SyntaxKind.TaggedTemplateExpression: + return createTempVariable(TempVariableKind.Auto); + } + } + + function generateNameForModuleOrEnum(node: ModuleDeclaration | EnumDeclaration) { + const name = node.name; + // Use module/enum name itself if it is unique, otherwise make a unique variation + return isUniqueLocalName(name.text, node) ? name : makeUniqueName(name.text); + } + + function generateNameForImportOrExportDeclaration(node: ImportDeclaration | ExportDeclaration) { + const expr = getExternalModuleName(node); + const baseName = expr.kind === SyntaxKind.StringLiteral + ? escapeIdentifier(makeIdentifierFromModuleName((expr).text)) + : "module"; + return makeUniqueName(baseName); + } + + function generateNameForExportDefault() { + return makeUniqueName("default"); + } + + function generateNameForClassExpression() { + return makeUniqueName("class"); + } + + /** + * Records a hoisted variable declaration within a lexical environment. + */ + function hoistVariableDeclaration(name: Identifier): void { + if (!hoistedVariableDeclarations) { + hoistedVariableDeclarations = []; + } + + hoistedVariableDeclarations.push(createVariableDeclaration(name)); + } + + /** + * Records a hoisted function declaration within a lexical environment. + */ + function hoistFunctionDeclaration(func: FunctionDeclaration): void { + if (!hoistedFunctionDeclarations) { + hoistedFunctionDeclarations = []; + } + + hoistedFunctionDeclarations.push(func); + } + + /** + * Starts a new lexical environment. Any existing hoisted variable or function declarations + * are pushed onto a stack, and the related storage variables are reset. + */ + function startLexicalEnvironment(): void { + // Save the current lexical environment. Rather than resizing the array we adjust the + // stack size variable. This allows us to reuse existing array slots we've + // already allocated between transformations to avoid allocation and GC overhead during + // transformation. + lexicalEnvironmentVariableDeclarationsStack[lexicalEnvironmentStackOffset] = hoistedVariableDeclarations; + lexicalEnvironmentFunctionDeclarationsStack[lexicalEnvironmentStackOffset] = hoistedFunctionDeclarations; + lexicalEnvironmentStackOffset++; + + hoistedVariableDeclarations = undefined; + hoistedFunctionDeclarations = undefined; + } + + /** + * Ends a lexical environment. The previous set of hoisted declarations are restored and + * any hoisted declarations added in this environment are returned. + */ + function endLexicalEnvironment(): Statement[] { + let statements: Statement[]; + if (hoistedVariableDeclarations || hoistedFunctionDeclarations) { + statements = []; + if (hoistedFunctionDeclarations) { + for (const declaration of hoistedFunctionDeclarations) { + statements.push(declaration); + } + } + + if (hoistedVariableDeclarations) { + statements.push( + createVariableStatement( + createVariableDeclarationList(hoistedVariableDeclarations) + ) + ); + } + } + + // Restore the previous lexical environment. + lexicalEnvironmentStackOffset--; + hoistedVariableDeclarations = lexicalEnvironmentVariableDeclarationsStack[lexicalEnvironmentStackOffset]; + hoistedFunctionDeclarations = lexicalEnvironmentFunctionDeclarationsStack[lexicalEnvironmentStackOffset]; + return statements; + } + } + + /** + * High-order function, creates a function that executes a function composition. + * For example, `chain(a, b)` is the equivalent of `x => ((a', b') => y => b'(a'(y)))(a(x), b(x))` + * + * @param args The functions to chain. + */ + function chain(...args: ((t: T) => (u: U) => U)[]): (t: T) => (u: U) => U; + function chain(a: (t: T) => (u: U) => U, b: (t: T) => (u: U) => U, c: (t: T) => (u: U) => U, d: (t: T) => (u: U) => U, e: (t: T) => (u: U) => U): (t: T) => (u: U) => U { + if (e) { + const args = arrayOf<(t: T) => (u: U) => U>(arguments); + return t => compose(...map(args, f => f(t))); + } + else if (d) { + return t => compose(a(t), b(t), c(t), d(t)); + } + else if (c) { + return t => compose(a(t), b(t), c(t)); + } + else if (b) { + return t => compose(a(t), b(t)); + } + else if (a) { + return t => compose(a(t)); + } + else { + return t => u => u; + } + } + + /** + * High-order function, composes functions. Note that functions are composed inside-out; + * for example, `compose(a, b)` is the equivalent of `x => b(a(x))`. + * + * @param args The functions to compose. + */ + function compose(...args: ((t: T) => T)[]): (t: T) => T; + function compose(a: (t: T) => T, b: (t: T) => T, c: (t: T) => T, d: (t: T) => T, e: (t: T) => T): (t: T) => T { + if (e) { + const args = arrayOf(arguments); + return t => reduceLeft<(t: T) => T, T>(args, (u, f) => f(u), t); + } + else if (d) { + return t => d(c(b(a(t)))); + } + else if (c) { + return t => c(b(a(t))); + } + else if (b) { + return t => b(a(t)); + } + else if (a) { + return t => a(t); + } + else { + return t => t; + } + } + + /** + * Makes an array from an ArrayLike. + */ + function arrayOf(arrayLike: ArrayLike) { + const array: T[] = []; + for (let i = 0; i < arrayLike.length; i++) { + array[i] = arrayLike[i]; + } + return array; + } +} \ No newline at end of file diff --git a/src/compiler/tsconfig.json b/src/compiler/tsconfig.json index 9d96dfbbb21..29f39b983f3 100644 --- a/src/compiler/tsconfig.json +++ b/src/compiler/tsconfig.json @@ -19,6 +19,7 @@ "checker.ts", "factory.ts", "visitor.ts", + "transformer.ts", "emitter.ts", "program.ts", "commandLineParser.ts", diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 0b020839124..39ee80b7b00 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -495,9 +495,15 @@ namespace ts { // @kind(SyntaxKind.StaticKeyword) export interface Modifier extends Node { } + export const enum TempVariableKind { + Auto, // Automatically generated identifier + Loop, // Automatically generated identifier with a preference for '_i' + } + // @kind(SyntaxKind.Identifier) export interface Identifier extends PrimaryExpression { text: string; // Text of identifier (with escapes converted to characters) + tempKind?: TempVariableKind; // Specifies whether to auto-generate the text for an identifier. originalKeywordKind?: SyntaxKind; // Original syntaxKind which get set so that we can report an error later } @@ -2782,6 +2788,34 @@ namespace ts { ArrayLiteralOrCallOrNewExcludes = ContainsSpreadElementExpression, } + /* @internal */ + export const enum NodeEmitFlags { + EmitHelpers = 1 << 0, + EmitExportStar = 1 << 1, + UMDDefine = 1 << 2, + NoLexicalEnvironment = 1 << 3, + SingleLine = 1 << 4, + } + + /* @internal */ + export interface TransformationContext extends LexicalEnvironment { + getCompilerOptions(): CompilerOptions; + getEmitResolver(): EmitResolver; + getNodeEmitFlags(node: Node): NodeEmitFlags; + setNodeEmitFlags(node: Node, flags: NodeEmitFlags): void; + hoistFunctionDeclaration(node: FunctionDeclaration): void; + hoistVariableDeclaration(node: Identifier): void; + isUniqueName(name: string): boolean; + getGeneratedNameForNode(node: Node): Identifier; + nodeHasGeneratedName(node: Node): boolean; + makeUniqueName(baseName: string): Identifier; + identifierSubstitution?: (node: Identifier) => Identifier; + expressionSubstitution?: (node: Expression) => Expression; + } + + /* @internal */ + export type Transformer = (context: TransformationContext) => (node: SourceFile) => SourceFile; + export interface TextSpan { start: number; length: number; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 75d0521a8b3..0f1d27a28f2 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1607,6 +1607,202 @@ namespace ts { return node ? getNodeId(node) : 0; } + export const enum Associativity { + Left, + Right + } + + export function getExpressionAssociativity(expression: Expression) { + const operator = getOperator(expression); + const hasArguments = expression.kind === SyntaxKind.NewExpression && (expression).arguments !== undefined; + return getOperatorAssociativity(expression.kind, operator, hasArguments); + } + + export function getOperatorAssociativity(kind: SyntaxKind, operator: SyntaxKind, hasArguments?: boolean) { + switch (kind) { + case SyntaxKind.NewExpression: + return hasArguments ? Associativity.Left : Associativity.Right; + + case SyntaxKind.PrefixUnaryExpression: + case SyntaxKind.TypeOfExpression: + case SyntaxKind.VoidExpression: + case SyntaxKind.DeleteExpression: + case SyntaxKind.AwaitExpression: + case SyntaxKind.ConditionalExpression: + case SyntaxKind.YieldExpression: + return Associativity.Right; + + case SyntaxKind.BinaryExpression: + switch (operator) { + case SyntaxKind.AsteriskAsteriskToken: + case SyntaxKind.EqualsToken: + case SyntaxKind.PlusEqualsToken: + case SyntaxKind.MinusEqualsToken: + case SyntaxKind.AsteriskAsteriskEqualsToken: + case SyntaxKind.AsteriskEqualsToken: + case SyntaxKind.SlashEqualsToken: + case SyntaxKind.PercentEqualsToken: + case SyntaxKind.LessThanLessThanEqualsToken: + case SyntaxKind.GreaterThanGreaterThanEqualsToken: + case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: + case SyntaxKind.AmpersandEqualsToken: + case SyntaxKind.CaretEqualsToken: + case SyntaxKind.BarEqualsToken: + return Associativity.Right; + } + } + + return Associativity.Left; + } + + export function getExpressionPrecedence(expression: Expression) { + const operator = getOperator(expression); + const hasArguments = expression.kind === SyntaxKind.NewExpression && (expression).arguments !== undefined; + return getOperatorPrecedence(expression.kind, operator, hasArguments); + } + + function getOperator(expression: Expression) { + if (expression.kind === SyntaxKind.BinaryExpression) { + return (expression).operatorToken.kind; + } + else if (expression.kind === SyntaxKind.PrefixUnaryExpression || expression.kind === SyntaxKind.PostfixUnaryExpression) { + return (expression).operator; + } + else { + return expression.kind; + } + } + + export function getOperatorPrecedence(nodeKind: SyntaxKind, operatorKind: SyntaxKind, hasArguments?: boolean) { + switch (nodeKind) { + case SyntaxKind.ThisKeyword: + case SyntaxKind.SuperKeyword: + case SyntaxKind.Identifier: + case SyntaxKind.NullKeyword: + case SyntaxKind.TrueKeyword: + case SyntaxKind.FalseKeyword: + case SyntaxKind.NumericLiteral: + case SyntaxKind.StringLiteral: + case SyntaxKind.ArrayLiteralExpression: + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.ClassExpression: + case SyntaxKind.JsxElement: + case SyntaxKind.JsxSelfClosingElement: + case SyntaxKind.RegularExpressionLiteral: + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.TemplateExpression: + case SyntaxKind.ParenthesizedExpression: + return 19; + + case SyntaxKind.TaggedTemplateExpression: + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + return 18; + + case SyntaxKind.NewExpression: + return hasArguments ? 18 : 17; + + case SyntaxKind.CallExpression: + return 17; + + case SyntaxKind.PostfixUnaryExpression: + return 16; + + case SyntaxKind.PrefixUnaryExpression: + case SyntaxKind.TypeOfExpression: + case SyntaxKind.VoidExpression: + case SyntaxKind.DeleteExpression: + case SyntaxKind.AwaitExpression: + return 15; + + case SyntaxKind.BinaryExpression: + switch (operatorKind) { + case SyntaxKind.ExclamationToken: + case SyntaxKind.TildeToken: + return 15; + + case SyntaxKind.AsteriskAsteriskToken: + case SyntaxKind.AsteriskToken: + case SyntaxKind.SlashToken: + case SyntaxKind.PercentToken: + return 14; + + case SyntaxKind.PlusToken: + case SyntaxKind.MinusToken: + return 13; + + case SyntaxKind.LessThanLessThanToken: + case SyntaxKind.GreaterThanGreaterThanToken: + case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: + return 12; + + case SyntaxKind.LessThanToken: + case SyntaxKind.LessThanEqualsToken: + case SyntaxKind.GreaterThanToken: + case SyntaxKind.GreaterThanEqualsToken: + case SyntaxKind.InKeyword: + case SyntaxKind.InstanceOfKeyword: + return 11; + + case SyntaxKind.EqualsEqualsToken: + case SyntaxKind.EqualsEqualsEqualsToken: + case SyntaxKind.ExclamationEqualsToken: + case SyntaxKind.ExclamationEqualsEqualsToken: + return 10; + + case SyntaxKind.AmpersandToken: + return 9; + + case SyntaxKind.CaretToken: + return 8; + + case SyntaxKind.BarToken: + return 7; + + case SyntaxKind.AmpersandAmpersandToken: + return 6; + + case SyntaxKind.BarBarToken: + return 5; + + case SyntaxKind.EqualsToken: + case SyntaxKind.PlusEqualsToken: + case SyntaxKind.MinusEqualsToken: + case SyntaxKind.AsteriskAsteriskEqualsToken: + case SyntaxKind.AsteriskEqualsToken: + case SyntaxKind.SlashEqualsToken: + case SyntaxKind.PercentEqualsToken: + case SyntaxKind.LessThanLessThanEqualsToken: + case SyntaxKind.GreaterThanGreaterThanEqualsToken: + case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: + case SyntaxKind.AmpersandEqualsToken: + case SyntaxKind.CaretEqualsToken: + case SyntaxKind.BarEqualsToken: + return 3; + + case SyntaxKind.CommaToken: + return 0; + + default: + return -1; + } + + case SyntaxKind.ConditionalExpression: + return 4; + + case SyntaxKind.YieldExpression: + return 2; + + case SyntaxKind.SpreadElementExpression: + return 1; + + default: + return -1; + } + } + export function createDiagnosticCollection(): DiagnosticCollection { let nonFileDiagnostics: Diagnostic[] = []; const fileDiagnostics: Map = {};