From 49ae28105996260dbdda15a4d13f925fd61ad55d Mon Sep 17 00:00:00 2001 From: Jason Freeman Date: Mon, 15 Sep 2014 17:03:08 -0700 Subject: [PATCH] Start testing signature counts and adjust some syntactic computations --- src/compiler/checker.ts | 6 +- src/services/services.ts | 65 +++++++------ src/services/signatureInfoHelpers.ts | 140 ++++++++++++--------------- tests/cases/fourslash/fourslash.ts | 2 +- 4 files changed, 104 insertions(+), 109 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 559fdf6f90a..f720cd40d29 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4435,7 +4435,11 @@ module ts { // must fill it up with the appropriate candidate signatures function getResolvedSignature(node: CallExpression, candidatesOutArray?: Signature[]): Signature { var links = getNodeLinks(node); - if (!links.resolvedSignature) { + // If getResolvedSignature has already been called, we will have cached the resolvedSignature. + // However, it is possible that either candidatesOutArray was not passed in the first time, + // or that a different candidatesOutArray was passed in. Therefore, we need to redo the work + // to correctly fill the candidatesOutArray. + if (!links.resolvedSignature || candidatesOutArray) { links.resolvedSignature = anySignature; links.resolvedSignature = node.kind === SyntaxKind.CallExpression ? resolveCallExpression(node, candidatesOutArray) diff --git a/src/services/services.ts b/src/services/services.ts index 91ba4070b29..6b9dbacda73 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -96,7 +96,6 @@ module ts { public flags: NodeFlags; public parent: Node; private _children: Node[]; - private _syntheticParent: Node; public getSourceFile(): SourceFile { var node: Node = this; @@ -152,9 +151,6 @@ module ts { if (pos < node.pos) { pos = this.addSyntheticNodes(list._children, pos, node.pos); } - else { - (node)._syntheticParent = list; - } list._children.push(node); pos = node.end; } @@ -207,11 +203,6 @@ module ts { return this._children; } - public getIndexOfChild(child: Node): number { - if (!this._children) this.createChildren(); - return this._children.indexOf(child); - } - public getFirstToken(sourceFile?: SourceFile): Node { var children = this.getChildren(); for (var i = 0; i < children.length; i++) { @@ -229,10 +220,6 @@ module ts { if (child.kind > SyntaxKind.Missing) return child.getLastToken(sourceFile); } } - - public getSyntheticParentOrParent(): Node { - return this._syntheticParent || this.parent; - } } class SymbolObject implements Symbol { @@ -3496,26 +3483,43 @@ module ts { // If node is an argument, returns its index in the argument list // If not, returns -1 function getArgumentIndex(node: Node): number { - // Treat the open paren / angle bracket of a call as the introduction of parameter slot 0 - var parent = (node).getSyntheticParentOrParent(); - if (parent.kind === SyntaxKind.SyntaxList) { - var grandparent = parent.parent; - if (grandparent.kind === SyntaxKind.CallExpression || grandparent.kind === SyntaxKind.NewExpression) { - var index = (parent).getIndexOfChild(node); - Debug.assert(index >= 0); - return index; - } + if (node.parent.kind !== SyntaxKind.CallExpression && node.parent.kind !== SyntaxKind.NewExpression) { + return -1; } + var parent = node.parent; + // Find out if 'node' is an argument, a type argument, or neither + // Treat the open paren / angle bracket of a call as the introduction of parameter slot 0 if (node.kind === SyntaxKind.LessThanToken || node.kind === SyntaxKind.OpenParenToken) { - return parent.kind === SyntaxKind.CallExpression || parent.kind === SyntaxKind.NewExpression - ? 0 - : -1; + return 0; } + + var argumentListOrTypeArgumentList: NodeArray; + if (parent.typeArguments && node.pos >= parent.typeArguments.pos && node.end <= parent.typeArguments.end) { + argumentListOrTypeArgumentList = parent.typeArguments; + } + else if (parent.arguments && node.pos >= parent.arguments.pos && node.end <= parent.arguments.end) { + argumentListOrTypeArgumentList = parent.arguments; + } + + return argumentListOrTypeArgumentList ? argumentListOrTypeArgumentList.indexOf(node) : -1; + + // if (parent.kind === SyntaxKind.SyntaxList) { + // var grandparent = parent.parent; + // if (grandparent.kind === SyntaxKind.CallExpression || grandparent.kind === SyntaxKind.NewExpression) { + // var index = (parent).getIndexOfChild(node); + // Debug.assert(index >= 0); + // return index; + // } + // } + + // if (node.kind === SyntaxKind.LessThanToken || node.kind === SyntaxKind.OpenParenToken) { + // return parent.kind === SyntaxKind.CallExpression || parent.kind === SyntaxKind.NewExpression + // ? 0 + // : -1; + // } // TODO: Handle close paren or close angle bracket on nonempty list - - return -1; } function getSignatureHelpArgumentContext(node: Node): { @@ -3529,7 +3533,7 @@ module ts { if (!isToken || position <= node.getStart() || position >= node.getEnd()) { // This is a temporary hack until we figure out our token story. // The correct solution is to get the previous token - node = SignatureInfoHelpers.findClosestRightmostSiblingFromLeft(position, sourceFile); + node = SignatureInfoHelpers.findPrecedingToken(position, sourceFile); if (!node) { return undefined; @@ -3559,9 +3563,8 @@ module ts { // TODO: Handle previous token logic // TODO: Handle generic call with incomplete - - return undefined; } + return undefined; } synchronizeHostData(); @@ -3578,7 +3581,7 @@ module ts { var candidates = []; var resolvedSignature = typeInfoResolver.getResolvedSignature(call, candidates); return candidates.length - ? new SignatureHelpItems(undefined, undefined, undefined) + ? new SignatureHelpItems(new Array(candidates.length), undefined, undefined) : undefined; } diff --git a/src/services/signatureInfoHelpers.ts b/src/services/signatureInfoHelpers.ts index 875aa3cfcd7..148e7af4f4a 100644 --- a/src/services/signatureInfoHelpers.ts +++ b/src/services/signatureInfoHelpers.ts @@ -345,82 +345,70 @@ module ts { // return null; //} - export function findClosestRightmostSiblingFromLeft(position: number, sourceFile: SourceFile): Node { - return search(sourceFile); - - function search(n: Node): Node { - - var childCandidate: Node; - forEachChild(n, c => { - if (c.kind === SyntaxKind.OmittedExpression || c.kind === SyntaxKind.Missing) { - return; - } - if (c.end <= position || c.getStart() < position) { - childCandidate = c; - } - return c.end > position; - }); - - if (childCandidate) { - if (childCandidate.end > position || !isCompletedNode(childCandidate, position, sourceFile)) { - return search(childCandidate) || childCandidate; - } - else { - return childCandidate; - } - } - } + export function findPrecedingToken(position: number, sourceFile: SourceFile): Node { + return find(sourceFile, /*diveIntoLastChild*/ false); + + function find(n: Node, diveIntoLastChild: boolean): Node { + if (isToken(n)) { + return n; + } + + var children = n.getChildren(); + if (diveIntoLastChild) { + var candidate = findLastChildNodeCandidate(children, /*exclusiveStartPosition*/ children.length); + return candidate && find(candidate, /*diveIntoLastChild*/ true); + } + + for (var i = 0, len = children.length; i < len; ++i) { + var child = children[i]; + if (nodeHasTokens(child)) { + if (position < child.end) { + if (child.getStart() >= position) { + // actual start of the node is past the position - previous token should be at the end of previous child + var candidate = findLastChildNodeCandidate(children, /*exclusiveStartPosition*/ i); + return candidate && find(candidate, /*diveIntoLastChild*/ true) + } + else { + // candidate should be in this node + return find(child, diveIntoLastChild); + } + } + } + } + + // here we know that none of child token nodes embrace the position + // try to find the closest token on the left + if (children.length) { + var candidate = findLastChildNodeCandidate(children, /*exclusiveStartPosition*/ children.length); + return candidate && find(candidate, /*diveIntoLastChild*/ true); + } + } + + /// finds last node that is considered as candidate for search (isCandidate(node) === true) starting from 'exclusiveStartPosition' + function findLastChildNodeCandidate(children: Node[], exclusiveStartPosition: number): Node { + for (var i = exclusiveStartPosition - 1; i >= 0; --i) { + if (nodeHasTokens(children[i])) { + return children[i]; + } + } + } + + function isToken(n: Node): boolean { + return n.kind < SyntaxKind.Missing; + } + + function nodeHasTokens(n: Node): boolean { + if (n.kind === SyntaxKind.ExpressionStatement) { + return nodeHasTokens((n).expression); + } + + if (n.kind === SyntaxKind.EndOfFileToken || n.kind === SyntaxKind.OmittedExpression || n.kind === SyntaxKind.Missing) { + return false; + } + + // SyntaxList is already realized so getChildCount should be fast and non-expensive + return n.kind !== SyntaxKind.SyntaxList || n.getChildCount() !== 0; + } } - - function isCompletedNode(n: Node, position: number, sourceFile: SourceFile): boolean { - switch (n.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.ObjectLiteral: - case SyntaxKind.Block: - case SyntaxKind.CatchBlock: - case SyntaxKind.FinallyBlock: - case SyntaxKind.FunctionBlock: - case SyntaxKind.ModuleBlock: - return isNodeEndWith(n, sourceFile, CharacterCodes.closeBrace); - case SyntaxKind.ParenExpression: - case SyntaxKind.CallSignature: - case SyntaxKind.CallExpression: - return isNodeEndWith(n, sourceFile, CharacterCodes.closeParen); - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.Method: - case SyntaxKind.ArrowFunction: - return !(n).body || isCompletedNode((n).body, position, sourceFile); - case SyntaxKind.ModuleDeclaration: - return (n).body && isCompletedNode((n).body, position, sourceFile); - case SyntaxKind.IfStatement: - if ((n).thenStatement && (n).thenStatement.end > position) { - return isCompletedNode((n).thenStatement, position, sourceFile); - } - else if ((n).elseStatement) { - return isCompletedNode((n).elseStatement, position, sourceFile); - } - else { - return true; - } - break; - case SyntaxKind.ExpressionStatement: - return isCompletedNode((n).expression, position, sourceFile); - case SyntaxKind.ArrayLiteral: - return isNodeEndWith(n, sourceFile, CharacterCodes.closeBracket); - case SyntaxKind.Missing: - return false; - default: - return true; - } - } - - function isNodeEndWith(n: Node, sourceFile: SourceFile, charCode: CharacterCodes): boolean { - var pos = n.end - 1; // Node.end is exclusive - return pos < sourceFile.text.length && sourceFile.text.charCodeAt(pos) === charCode; - } - } } \ No newline at end of file diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 2b2a0d60172..c188d558848 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -291,7 +291,7 @@ module FourSlashInterface { } public signatureHelpCountIs(expected: number) { - // FourSlash.currentTestState.verifySignatureHelpCount(expected); + FourSlash.currentTestState.verifySignatureHelpCount(expected); } public currentSignatureParamterCountIs(expected: number) {