mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Merge pull request #726 from Microsoft/sigHelp
Signature help in the new compiler
This commit is contained in:
+111
-62
@@ -91,6 +91,7 @@ module ts {
|
||||
getRootSymbol: getRootSymbol,
|
||||
getContextualType: getContextualType,
|
||||
getFullyQualifiedName: getFullyQualifiedName,
|
||||
getResolvedSignature: getResolvedSignature,
|
||||
getEnumMemberValue: getEnumMemberValue
|
||||
};
|
||||
|
||||
@@ -4330,55 +4331,26 @@ module ts {
|
||||
return unknownSignature;
|
||||
}
|
||||
|
||||
function isCandidateSignature(node: CallExpression, signature: Signature) {
|
||||
function signatureHasCorrectArity(node: CallExpression, signature: Signature): boolean {
|
||||
var args = node.arguments || emptyArray;
|
||||
return args.length >= signature.minArgumentCount &&
|
||||
var isCorrect = args.length >= signature.minArgumentCount &&
|
||||
(signature.hasRestParameter || args.length <= signature.parameters.length) &&
|
||||
(!node.typeArguments || signature.typeParameters && node.typeArguments.length === signature.typeParameters.length);
|
||||
}
|
||||
|
||||
// The candidate list orders groups in reverse, but within a group signatures are kept in declaration order
|
||||
// A nit here is that we reorder only signatures that belong to the same symbol,
|
||||
// so order how inherited signatures are processed is still preserved.
|
||||
// interface A { (x: string): void }
|
||||
// interface B extends A { (x: 'foo'): string }
|
||||
// var b: B;
|
||||
// b('foo') // <- here overloads should be processed as [(x:'foo'): string, (x: string): void]
|
||||
function collectCandidates(node: CallExpression, signatures: Signature[]): Signature[]{
|
||||
var result: Signature[] = [];
|
||||
var lastParent: Node;
|
||||
var lastSymbol: Symbol;
|
||||
var cutoffPos: number = 0;
|
||||
var pos: number;
|
||||
for (var i = 0; i < signatures.length; i++) {
|
||||
var signature = signatures[i];
|
||||
if (isCandidateSignature(node, signature)) {
|
||||
var symbol = signature.declaration && getSymbolOfNode(signature.declaration);
|
||||
var parent = signature.declaration && signature.declaration.parent;
|
||||
if (!lastSymbol || symbol === lastSymbol) {
|
||||
if (lastParent && parent === lastParent) {
|
||||
pos++;
|
||||
}
|
||||
else {
|
||||
lastParent = parent;
|
||||
pos = cutoffPos;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// current declaration belongs to a different symbol
|
||||
// set cutoffPos so re-orderings in the future won't change result set from 0 to cutoffPos
|
||||
pos = cutoffPos = result.length;
|
||||
lastParent = parent;
|
||||
}
|
||||
lastSymbol = symbol;
|
||||
|
||||
for (var j = result.length; j > pos; j--) {
|
||||
result[j] = result[j - 1];
|
||||
}
|
||||
result[pos] = signature;
|
||||
// For error recovery, since we have parsed OmittedExpressions for any extra commas
|
||||
// in the argument list, if we see any OmittedExpressions, just return true.
|
||||
// The reason this is ok is because omitted expressions here are syntactically
|
||||
// illegal, and will cause a parse error.
|
||||
// Note: It may be worth keeping the upper bound check on arity, but removing
|
||||
// the lower bound check if there are omitted expressions.
|
||||
if (!isCorrect) {
|
||||
// Technically this type assertion is not safe because args could be initialized to emptyArray
|
||||
// above.
|
||||
if ((<NodeArray<Node>>args).hasTrailingComma || forEach(args, arg => arg.kind === SyntaxKind.OmittedExpression)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return isCorrect;
|
||||
}
|
||||
|
||||
// If type has a single call signature and no other members, return that signature. Otherwise, return undefined.
|
||||
@@ -4409,6 +4381,9 @@ module ts {
|
||||
var mapper = createInferenceMapper(context);
|
||||
// First infer from arguments that are not context sensitive
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
if (args[i].kind === SyntaxKind.OmittedExpression) {
|
||||
continue;
|
||||
}
|
||||
if (!excludeArgument || excludeArgument[i] === undefined) {
|
||||
var parameterType = getTypeAtPosition(signature, i);
|
||||
inferTypes(context, checkExpressionWithContextualType(args[i], parameterType, mapper), parameterType);
|
||||
@@ -4417,6 +4392,9 @@ module ts {
|
||||
// Next, infer from those context sensitive arguments that are no longer excluded
|
||||
if (excludeArgument) {
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
if (args[i].kind === SyntaxKind.OmittedExpression) {
|
||||
continue;
|
||||
}
|
||||
if (excludeArgument[i] === false) {
|
||||
var parameterType = getTypeAtPosition(signature, i);
|
||||
inferTypes(context, checkExpressionWithContextualType(args[i], parameterType, mapper), parameterType);
|
||||
@@ -4445,6 +4423,10 @@ module ts {
|
||||
if (node.arguments) {
|
||||
for (var i = 0; i < node.arguments.length; i++) {
|
||||
var arg = node.arguments[i];
|
||||
if (arg.kind === SyntaxKind.OmittedExpression) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var paramType = getTypeAtPosition(signature, i);
|
||||
// String literals get string literal types unless we're reporting errors
|
||||
var argType = arg.kind === SyntaxKind.StringLiteral && !reportErrors ?
|
||||
@@ -4462,9 +4444,11 @@ module ts {
|
||||
return true;
|
||||
}
|
||||
|
||||
function resolveCall(node: CallExpression, signatures: Signature[]): Signature {
|
||||
function resolveCall(node: CallExpression, signatures: Signature[], candidatesOutArray: Signature[]): Signature {
|
||||
forEach(node.typeArguments, checkSourceElement);
|
||||
var candidates = collectCandidates(node, signatures);
|
||||
var candidates = candidatesOutArray || [];
|
||||
// collectCandidates fills up the candidates array directly
|
||||
collectCandidates();
|
||||
if (!candidates.length) {
|
||||
error(node, Diagnostics.Supplied_parameters_do_not_match_any_signature_of_call_target);
|
||||
return resolveErrorCall(node);
|
||||
@@ -4480,20 +4464,24 @@ module ts {
|
||||
var relation = candidates.length === 1 ? assignableRelation : subtypeRelation;
|
||||
while (true) {
|
||||
for (var i = 0; i < candidates.length; i++) {
|
||||
if (!signatureHasCorrectArity(node, candidates[i])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
var candidate = candidates[i];
|
||||
if (candidate.typeParameters) {
|
||||
var candidateWithCorrectArity = candidates[i];
|
||||
if (candidateWithCorrectArity.typeParameters) {
|
||||
var typeArguments = node.typeArguments ?
|
||||
checkTypeArguments(candidate, node.typeArguments) :
|
||||
inferTypeArguments(candidate, args, excludeArgument);
|
||||
candidate = getSignatureInstantiation(candidate, typeArguments);
|
||||
checkTypeArguments(candidateWithCorrectArity, node.typeArguments) :
|
||||
inferTypeArguments(candidateWithCorrectArity, args, excludeArgument);
|
||||
candidateWithCorrectArity = getSignatureInstantiation(candidateWithCorrectArity, typeArguments);
|
||||
}
|
||||
if (!checkApplicableSignature(node, candidate, relation, excludeArgument, /*reportErrors*/ false)) {
|
||||
if (!checkApplicableSignature(node, candidateWithCorrectArity, relation, excludeArgument, /*reportErrors*/ false)) {
|
||||
break;
|
||||
}
|
||||
var index = excludeArgument ? indexOf(excludeArgument, true) : -1;
|
||||
if (index < 0) {
|
||||
return candidate;
|
||||
return candidateWithCorrectArity;
|
||||
}
|
||||
excludeArgument[index] = false;
|
||||
}
|
||||
@@ -4503,17 +4491,70 @@ module ts {
|
||||
}
|
||||
relation = assignableRelation;
|
||||
}
|
||||
|
||||
// No signatures were applicable. Now report errors based on the last applicable signature with
|
||||
// no arguments excluded from assignability checks.
|
||||
checkApplicableSignature(node, candidate, relation, undefined, /*reportErrors*/ true);
|
||||
// If candidate is undefined, it means that no candidates had a suitable arity. In that case,
|
||||
// skip the checkApplicableSignature check.
|
||||
if (candidateWithCorrectArity) {
|
||||
checkApplicableSignature(node, candidateWithCorrectArity, relation, /*excludeArgument*/ undefined, /*reportErrors*/ true);
|
||||
}
|
||||
else {
|
||||
error(node, Diagnostics.Supplied_parameters_do_not_match_any_signature_of_call_target);
|
||||
return resolveErrorCall(node);
|
||||
}
|
||||
return resolveErrorCall(node);
|
||||
|
||||
// The candidate list orders groups in reverse, but within a group signatures are kept in declaration order
|
||||
// A nit here is that we reorder only signatures that belong to the same symbol,
|
||||
// so order how inherited signatures are processed is still preserved.
|
||||
// interface A { (x: string): void }
|
||||
// interface B extends A { (x: 'foo'): string }
|
||||
// var b: B;
|
||||
// b('foo') // <- here overloads should be processed as [(x:'foo'): string, (x: string): void]
|
||||
function collectCandidates(): void {
|
||||
var result = candidates;
|
||||
var lastParent: Node;
|
||||
var lastSymbol: Symbol;
|
||||
var cutoffPos: number = 0;
|
||||
var pos: number;
|
||||
Debug.assert(!result.length);
|
||||
for (var i = 0; i < signatures.length; i++) {
|
||||
var signature = signatures[i];
|
||||
if (true) {
|
||||
var symbol = signature.declaration && getSymbolOfNode(signature.declaration);
|
||||
var parent = signature.declaration && signature.declaration.parent;
|
||||
if (!lastSymbol || symbol === lastSymbol) {
|
||||
if (lastParent && parent === lastParent) {
|
||||
pos++;
|
||||
}
|
||||
else {
|
||||
lastParent = parent;
|
||||
pos = cutoffPos;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// current declaration belongs to a different symbol
|
||||
// set cutoffPos so re-orderings in the future won't change result set from 0 to cutoffPos
|
||||
pos = cutoffPos = result.length;
|
||||
lastParent = parent;
|
||||
}
|
||||
lastSymbol = symbol;
|
||||
|
||||
for (var j = result.length; j > pos; j--) {
|
||||
result[j] = result[j - 1];
|
||||
}
|
||||
result[pos] = signature;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resolveCallExpression(node: CallExpression): Signature {
|
||||
function resolveCallExpression(node: CallExpression, candidatesOutArray: Signature[]): Signature {
|
||||
if (node.func.kind === SyntaxKind.SuperKeyword) {
|
||||
var superType = checkSuperExpression(node.func);
|
||||
if (superType !== unknownType) {
|
||||
return resolveCall(node, getSignaturesOfType(superType, SignatureKind.Construct));
|
||||
return resolveCall(node, getSignaturesOfType(superType, SignatureKind.Construct), candidatesOutArray);
|
||||
}
|
||||
return resolveUntypedCall(node);
|
||||
}
|
||||
@@ -4561,10 +4602,10 @@ module ts {
|
||||
}
|
||||
return resolveErrorCall(node);
|
||||
}
|
||||
return resolveCall(node, callSignatures);
|
||||
return resolveCall(node, callSignatures, candidatesOutArray);
|
||||
}
|
||||
|
||||
function resolveNewExpression(node: NewExpression): Signature {
|
||||
function resolveNewExpression(node: NewExpression, candidatesOutArray: Signature[]): Signature {
|
||||
var expressionType = checkExpression(node.func);
|
||||
if (expressionType === unknownType) {
|
||||
// Another error has already been reported
|
||||
@@ -4599,7 +4640,7 @@ module ts {
|
||||
// that the user will not add any.
|
||||
var constructSignatures = getSignaturesOfType(expressionType, SignatureKind.Construct);
|
||||
if (constructSignatures.length) {
|
||||
return resolveCall(node, constructSignatures);
|
||||
return resolveCall(node, constructSignatures, candidatesOutArray);
|
||||
}
|
||||
|
||||
// If ConstructExpr's apparent type is an object type with no construct signatures but
|
||||
@@ -4608,7 +4649,7 @@ module ts {
|
||||
// operation is Any.
|
||||
var callSignatures = getSignaturesOfType(expressionType, SignatureKind.Call);
|
||||
if (callSignatures.length) {
|
||||
var signature = resolveCall(node, callSignatures);
|
||||
var signature = resolveCall(node, callSignatures, candidatesOutArray);
|
||||
if (getReturnTypeOfSignature(signature) !== voidType) {
|
||||
error(node, Diagnostics.Only_a_void_function_can_be_called_with_the_new_keyword);
|
||||
}
|
||||
@@ -4619,11 +4660,19 @@ module ts {
|
||||
return resolveErrorCall(node);
|
||||
}
|
||||
|
||||
function getResolvedSignature(node: CallExpression): Signature {
|
||||
// candidatesOutArray is passed by signature help in the language service, and collectCandidates
|
||||
// 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) : resolveNewExpression(node);
|
||||
links.resolvedSignature = node.kind === SyntaxKind.CallExpression
|
||||
? resolveCallExpression(node, candidatesOutArray)
|
||||
: resolveNewExpression(node, candidatesOutArray);
|
||||
}
|
||||
return links.resolvedSignature;
|
||||
}
|
||||
|
||||
+15
-1
@@ -11,7 +11,9 @@ module ts {
|
||||
var result: U;
|
||||
if (array) {
|
||||
for (var i = 0, len = array.length; i < len; i++) {
|
||||
if (result = callback(array[i])) break;
|
||||
if (result = callback(array[i])) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -39,6 +41,18 @@ module ts {
|
||||
return -1;
|
||||
}
|
||||
|
||||
export function countWhere<T>(array: T[], predicate: (x: T) => boolean): number {
|
||||
var count = 0;
|
||||
if (array) {
|
||||
for (var i = 0, len = array.length; i < len; i++) {
|
||||
if (predicate(array[i])) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
export function filter<T>(array: T[], f: (x: T) => boolean): T[] {
|
||||
if (array) {
|
||||
var result: T[] = [];
|
||||
|
||||
+41
-18
@@ -748,23 +748,46 @@ module ts {
|
||||
}
|
||||
}
|
||||
|
||||
function emitCommaList(nodes: Node[], count?: number) {
|
||||
if (!(count >= 0)) count = nodes.length;
|
||||
if (nodes) {
|
||||
for (var i = 0; i < count; i++) {
|
||||
if (i) write(", ");
|
||||
emit(nodes[i]);
|
||||
function emitTrailingCommaIfPresent(nodeList: NodeArray<Node>, isMultiline: boolean): void {
|
||||
if (nodeList.hasTrailingComma) {
|
||||
write(",");
|
||||
if (isMultiline) {
|
||||
writeLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function emitMultiLineList(nodes: Node[]) {
|
||||
function emitCommaList(nodes: NodeArray<Node>, includeTrailingComma: boolean, count?: number) {
|
||||
if (!(count >= 0)) {
|
||||
count = nodes.length;
|
||||
}
|
||||
if (nodes) {
|
||||
for (var i = 0; i < count; i++) {
|
||||
if (i) {
|
||||
write(", ");
|
||||
}
|
||||
emit(nodes[i]);
|
||||
}
|
||||
|
||||
if (includeTrailingComma) {
|
||||
emitTrailingCommaIfPresent(nodes, /*isMultiline*/ false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function emitMultiLineList(nodes: NodeArray<Node>, includeTrailingComma: boolean) {
|
||||
if (nodes) {
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
if (i) write(",");
|
||||
if (i) {
|
||||
write(",");
|
||||
}
|
||||
writeLine();
|
||||
emit(nodes[i]);
|
||||
}
|
||||
|
||||
if (includeTrailingComma) {
|
||||
emitTrailingCommaIfPresent(nodes, /*isMultiline*/ true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -876,14 +899,14 @@ module ts {
|
||||
if (node.flags & NodeFlags.MultiLine) {
|
||||
write("[");
|
||||
increaseIndent();
|
||||
emitMultiLineList(node.elements);
|
||||
emitMultiLineList(node.elements, /*includeTrailingComma*/ true);
|
||||
decreaseIndent();
|
||||
writeLine();
|
||||
write("]");
|
||||
}
|
||||
else {
|
||||
write("[");
|
||||
emitCommaList(node.elements);
|
||||
emitCommaList(node.elements, /*includeTrailingComma*/ true);
|
||||
write("]");
|
||||
}
|
||||
}
|
||||
@@ -895,14 +918,14 @@ module ts {
|
||||
else if (node.flags & NodeFlags.MultiLine) {
|
||||
write("{");
|
||||
increaseIndent();
|
||||
emitMultiLineList(node.properties);
|
||||
emitMultiLineList(node.properties, /*includeTrailingComma*/ compilerOptions.target >= ScriptTarget.ES5);
|
||||
decreaseIndent();
|
||||
writeLine();
|
||||
write("}");
|
||||
}
|
||||
else {
|
||||
write("{ ");
|
||||
emitCommaList(node.properties);
|
||||
emitCommaList(node.properties, /*includeTrailingComma*/ compilerOptions.target >= ScriptTarget.ES5);
|
||||
write(" }");
|
||||
}
|
||||
}
|
||||
@@ -949,13 +972,13 @@ module ts {
|
||||
emitThis(node.func);
|
||||
if (node.arguments.length) {
|
||||
write(", ");
|
||||
emitCommaList(node.arguments);
|
||||
emitCommaList(node.arguments, /*includeTrailingComma*/ false);
|
||||
}
|
||||
write(")");
|
||||
}
|
||||
else {
|
||||
write("(");
|
||||
emitCommaList(node.arguments);
|
||||
emitCommaList(node.arguments, /*includeTrailingComma*/ false);
|
||||
write(")");
|
||||
}
|
||||
}
|
||||
@@ -965,7 +988,7 @@ module ts {
|
||||
emit(node.func);
|
||||
if (node.arguments) {
|
||||
write("(");
|
||||
emitCommaList(node.arguments);
|
||||
emitCommaList(node.arguments, /*includeTrailingComma*/ false);
|
||||
write(")");
|
||||
}
|
||||
}
|
||||
@@ -1138,7 +1161,7 @@ module ts {
|
||||
if (node.declarations) {
|
||||
emitToken(SyntaxKind.VarKeyword, endPos);
|
||||
write(" ");
|
||||
emitCommaList(node.declarations);
|
||||
emitCommaList(node.declarations, /*includeTrailingComma*/ false);
|
||||
}
|
||||
if (node.initializer) {
|
||||
emit(node.initializer);
|
||||
@@ -1286,7 +1309,7 @@ module ts {
|
||||
function emitVariableStatement(node: VariableStatement) {
|
||||
emitLeadingComments(node);
|
||||
if (!(node.flags & NodeFlags.Export)) write("var ");
|
||||
emitCommaList(node.declarations);
|
||||
emitCommaList(node.declarations, /*includeTrailingComma*/ false);
|
||||
write(";");
|
||||
emitTrailingComments(node);
|
||||
}
|
||||
@@ -1395,7 +1418,7 @@ module ts {
|
||||
increaseIndent();
|
||||
write("(");
|
||||
if (node) {
|
||||
emitCommaList(node.parameters, node.parameters.length - (hasRestParameters(node) ? 1 : 0));
|
||||
emitCommaList(node.parameters, /*includeTrailingComma*/ false, node.parameters.length - (hasRestParameters(node) ? 1 : 0));
|
||||
}
|
||||
write(")");
|
||||
decreaseIndent();
|
||||
|
||||
+44
-24
@@ -628,12 +628,6 @@ module ts {
|
||||
Parameters, // Parameters in parameter list
|
||||
}
|
||||
|
||||
enum TrailingCommaBehavior {
|
||||
Disallow,
|
||||
Allow,
|
||||
Preserve
|
||||
}
|
||||
|
||||
// Tracks whether we nested (directly or indirectly) in a certain control block.
|
||||
// Used for validating break and continue statements.
|
||||
enum ControlBlockContext {
|
||||
@@ -1205,7 +1199,7 @@ module ts {
|
||||
}
|
||||
|
||||
// Parses a comma-delimited list of elements
|
||||
function parseDelimitedList<T extends Node>(kind: ParsingContext, parseElement: () => T, trailingCommaBehavior: TrailingCommaBehavior): NodeArray<T> {
|
||||
function parseDelimitedList<T extends Node>(kind: ParsingContext, parseElement: () => T, allowTrailingComma: boolean): NodeArray<T> {
|
||||
var saveParsingContext = parsingContext;
|
||||
parsingContext |= 1 << kind;
|
||||
var result = <NodeArray<T>>[];
|
||||
@@ -1230,15 +1224,14 @@ module ts {
|
||||
else if (isListTerminator(kind)) {
|
||||
// Check if the last token was a comma.
|
||||
if (commaStart >= 0) {
|
||||
if (trailingCommaBehavior === TrailingCommaBehavior.Disallow) {
|
||||
if (!allowTrailingComma) {
|
||||
if (file.syntacticErrors.length === errorCountBeforeParsingList) {
|
||||
// Report a grammar error so we don't affect lookahead
|
||||
grammarErrorAtPos(commaStart, scanner.getStartPos() - commaStart, Diagnostics.Trailing_comma_not_allowed);
|
||||
}
|
||||
}
|
||||
else if (trailingCommaBehavior === TrailingCommaBehavior.Preserve) {
|
||||
result.push(<T>createNode(SyntaxKind.OmittedExpression));
|
||||
}
|
||||
// Always preserve a trailing comma by marking it on the NodeArray
|
||||
result.hasTrailingComma = true;
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -1273,7 +1266,7 @@ module ts {
|
||||
|
||||
function parseBracketedList<T extends Node>(kind: ParsingContext, parseElement: () => T, startToken: SyntaxKind, endToken: SyntaxKind): NodeArray<T> {
|
||||
if (parseExpected(startToken)) {
|
||||
var result = parseDelimitedList(kind, parseElement, TrailingCommaBehavior.Disallow);
|
||||
var result = parseDelimitedList(kind, parseElement, /*allowTrailingComma*/ false);
|
||||
parseExpected(endToken);
|
||||
return result;
|
||||
}
|
||||
@@ -2309,7 +2302,11 @@ module ts {
|
||||
else {
|
||||
parseExpected(SyntaxKind.OpenParenToken);
|
||||
}
|
||||
callExpr.arguments = parseDelimitedList(ParsingContext.ArgumentExpressions, parseAssignmentExpression, TrailingCommaBehavior.Disallow);
|
||||
// It is an error to have a trailing comma in an argument list. However, the checker
|
||||
// needs evidence of a trailing comma in order to give good results for signature help.
|
||||
// That is why we do not allow a trailing comma, but we "preserve" a trailing comma.
|
||||
callExpr.arguments = parseDelimitedList(ParsingContext.ArgumentExpressions,
|
||||
parseArgumentExpression, /*allowTrailingComma*/ false);
|
||||
parseExpected(SyntaxKind.CloseParenToken);
|
||||
expr = finishNode(callExpr);
|
||||
continue;
|
||||
@@ -2378,15 +2375,33 @@ module ts {
|
||||
return finishNode(node);
|
||||
}
|
||||
|
||||
function parseAssignmentExpressionOrOmittedExpression(omittedExpressionDiagnostic: DiagnosticMessage): Expression {
|
||||
if (token === SyntaxKind.CommaToken) {
|
||||
if (omittedExpressionDiagnostic) {
|
||||
var errorStart = scanner.getTokenPos();
|
||||
var errorLength = scanner.getTextPos() - errorStart;
|
||||
grammarErrorAtPos(errorStart, errorLength, omittedExpressionDiagnostic);
|
||||
}
|
||||
return createNode(SyntaxKind.OmittedExpression);
|
||||
}
|
||||
|
||||
return parseAssignmentExpression();
|
||||
}
|
||||
|
||||
function parseArrayLiteralElement(): Expression {
|
||||
return token === SyntaxKind.CommaToken ? createNode(SyntaxKind.OmittedExpression) : parseAssignmentExpression();
|
||||
return parseAssignmentExpressionOrOmittedExpression(/*omittedExpressionDiagnostic*/ undefined);
|
||||
}
|
||||
|
||||
function parseArgumentExpression(): Expression {
|
||||
return parseAssignmentExpressionOrOmittedExpression(Diagnostics.Argument_expression_expected);
|
||||
}
|
||||
|
||||
function parseArrayLiteral(): ArrayLiteral {
|
||||
var node = <ArrayLiteral>createNode(SyntaxKind.ArrayLiteral);
|
||||
parseExpected(SyntaxKind.OpenBracketToken);
|
||||
if (scanner.hasPrecedingLineBreak()) node.flags |= NodeFlags.MultiLine;
|
||||
node.elements = parseDelimitedList(ParsingContext.ArrayLiteralMembers, parseArrayLiteralElement, TrailingCommaBehavior.Preserve);
|
||||
node.elements = parseDelimitedList(ParsingContext.ArrayLiteralMembers,
|
||||
parseArrayLiteralElement, /*allowTrailingComma*/ true);
|
||||
parseExpected(SyntaxKind.CloseBracketToken);
|
||||
return finishNode(node);
|
||||
}
|
||||
@@ -2428,10 +2443,7 @@ module ts {
|
||||
node.flags |= NodeFlags.MultiLine;
|
||||
}
|
||||
|
||||
// ES3 itself does not accept a trailing comma in an object literal, however, we'd like to preserve it in ES5.
|
||||
var trailingCommaBehavior = languageVersion === ScriptTarget.ES3 ? TrailingCommaBehavior.Allow : TrailingCommaBehavior.Preserve;
|
||||
|
||||
node.properties = parseDelimitedList(ParsingContext.ObjectLiteralMembers, parseObjectLiteralMember, trailingCommaBehavior);
|
||||
node.properties = parseDelimitedList(ParsingContext.ObjectLiteralMembers, parseObjectLiteralMember, /*allowTrailingComma*/ true);
|
||||
parseExpected(SyntaxKind.CloseBraceToken);
|
||||
|
||||
var seen: Map<SymbolFlags> = {};
|
||||
@@ -2520,7 +2532,11 @@ module ts {
|
||||
parseExpected(SyntaxKind.NewKeyword);
|
||||
node.func = parseCallAndAccess(parsePrimaryExpression(), /* inNewExpression */ true);
|
||||
if (parseOptional(SyntaxKind.OpenParenToken) || token === SyntaxKind.LessThanToken && (node.typeArguments = tryParse(parseTypeArgumentsAndOpenParen))) {
|
||||
node.arguments = parseDelimitedList(ParsingContext.ArgumentExpressions, parseAssignmentExpression, TrailingCommaBehavior.Disallow);
|
||||
// It is an error to have a trailing comma in an argument list. However, the checker
|
||||
// needs evidence of a trailing comma in order to give good results for signature help.
|
||||
// That is why we do not allow a trailing comma, but we "preserve" a trailing comma.
|
||||
node.arguments = parseDelimitedList(ParsingContext.ArgumentExpressions,
|
||||
parseArgumentExpression, /*allowTrailingComma*/ false);
|
||||
parseExpected(SyntaxKind.CloseParenToken);
|
||||
}
|
||||
return finishNode(node);
|
||||
@@ -3089,7 +3105,8 @@ module ts {
|
||||
}
|
||||
|
||||
function parseVariableDeclarationList(flags: NodeFlags, noIn?: boolean): NodeArray<VariableDeclaration> {
|
||||
return parseDelimitedList(ParsingContext.VariableDeclarations, () => parseVariableDeclaration(flags, noIn), TrailingCommaBehavior.Disallow);
|
||||
return parseDelimitedList(ParsingContext.VariableDeclarations,
|
||||
() => parseVariableDeclaration(flags, noIn), /*allowTrailingComma*/ false);
|
||||
}
|
||||
|
||||
function parseVariableStatement(pos?: number, flags?: NodeFlags): VariableStatement {
|
||||
@@ -3488,7 +3505,8 @@ module ts {
|
||||
var implementsKeywordLength: number;
|
||||
if (parseOptional(SyntaxKind.ImplementsKeyword)) {
|
||||
implementsKeywordLength = scanner.getStartPos() - implementsKeywordStart;
|
||||
node.implementedTypes = parseDelimitedList(ParsingContext.BaseTypeReferences, parseTypeReference, TrailingCommaBehavior.Disallow);
|
||||
node.implementedTypes = parseDelimitedList(ParsingContext.BaseTypeReferences,
|
||||
parseTypeReference, /*allowTrailingComma*/ false);
|
||||
}
|
||||
var errorCountBeforeClassBody = file.syntacticErrors.length;
|
||||
if (parseExpected(SyntaxKind.OpenBraceToken)) {
|
||||
@@ -3516,7 +3534,8 @@ module ts {
|
||||
var extendsKeywordLength: number;
|
||||
if (parseOptional(SyntaxKind.ExtendsKeyword)) {
|
||||
extendsKeywordLength = scanner.getStartPos() - extendsKeywordStart;
|
||||
node.baseTypes = parseDelimitedList(ParsingContext.BaseTypeReferences, parseTypeReference, TrailingCommaBehavior.Disallow);
|
||||
node.baseTypes = parseDelimitedList(ParsingContext.BaseTypeReferences,
|
||||
parseTypeReference, /*allowTrailingComma*/ false);
|
||||
}
|
||||
var errorCountBeforeInterfaceBody = file.syntacticErrors.length;
|
||||
node.members = parseTypeLiteral().members;
|
||||
@@ -3580,7 +3599,8 @@ module ts {
|
||||
parseExpected(SyntaxKind.EnumKeyword);
|
||||
node.name = parseIdentifier();
|
||||
if (parseExpected(SyntaxKind.OpenBraceToken)) {
|
||||
node.members = parseDelimitedList(ParsingContext.EnumMembers, parseAndCheckEnumMember, TrailingCommaBehavior.Allow);
|
||||
node.members = parseDelimitedList(ParsingContext.EnumMembers,
|
||||
parseAndCheckEnumMember, /*allowTrailingComma*/ true);
|
||||
parseExpected(SyntaxKind.CloseBraceToken);
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -261,7 +261,9 @@ module ts {
|
||||
localSymbol?: Symbol; // Local symbol declared by node (initialized by binding only for exported nodes)
|
||||
}
|
||||
|
||||
export interface NodeArray<T> extends Array<T>, TextRange { }
|
||||
export interface NodeArray<T> extends Array<T>, TextRange {
|
||||
hasTrailingComma?: boolean;
|
||||
}
|
||||
|
||||
export interface Identifier extends Node {
|
||||
text: string; // Text of identifier (with escapes converted to characters)
|
||||
@@ -648,6 +650,7 @@ module ts {
|
||||
getAugmentedPropertiesOfApparentType(type: Type): Symbol[];
|
||||
getRootSymbol(symbol: Symbol): Symbol;
|
||||
getContextualType(node: Node): Type;
|
||||
getResolvedSignature(node: CallExpression, candidatesOutArray?: Signature[]): Signature;
|
||||
|
||||
// Returns the constant value of this enum member, or 'undefined' if the enum member has a
|
||||
// computed value.
|
||||
@@ -935,7 +938,7 @@ module ts {
|
||||
resolvedReturnType: Type; // Resolved return type
|
||||
minArgumentCount: number; // Number of non-optional parameters
|
||||
hasRestParameter: boolean; // True if last parameter is rest parameter
|
||||
hasStringLiterals: boolean; // True if instantiated
|
||||
hasStringLiterals: boolean; // True if specialized
|
||||
target?: Signature; // Instantiation target
|
||||
mapper?: TypeMapper; // Instantiation mapper
|
||||
erasedSignatureCache?: Signature; // Erased version of signature (deferred)
|
||||
|
||||
@@ -819,14 +819,14 @@ module FourSlash {
|
||||
public verifyCurrentSignatureHelpIs(expected: string) {
|
||||
this.taoInvalidReason = 'verifyCurrentSignatureHelpIs NYI';
|
||||
|
||||
var help = this.getActiveSignatureHelp();
|
||||
var help = this.getActiveSignatureHelpItem();
|
||||
assert.equal(help.prefix + help.parameters.map(p => p.display).join(help.separator) + help.suffix, expected);
|
||||
}
|
||||
|
||||
public verifyCurrentParameterIsVariable(isVariable: boolean) {
|
||||
this.taoInvalidReason = 'verifyCurrentParameterIsVariable NYI';
|
||||
|
||||
var signature = this.getActiveSignatureHelp();
|
||||
var signature = this.getActiveSignatureHelpItem();
|
||||
assert.isNotNull(signature);
|
||||
assert.equal(isVariable, signature.isVariadic);
|
||||
}
|
||||
@@ -842,7 +842,7 @@ module FourSlash {
|
||||
public verifyCurrentParameterSpanIs(parameter: string) {
|
||||
this.taoInvalidReason = 'verifyCurrentParameterSpanIs NYI';
|
||||
|
||||
var activeSignature = this.getActiveSignatureHelp();
|
||||
var activeSignature = this.getActiveSignatureHelpItem();
|
||||
var activeParameter = this.getActiveParameter();
|
||||
assert.equal(activeParameter.display, parameter);
|
||||
}
|
||||
@@ -858,19 +858,19 @@ module FourSlash {
|
||||
public verifyCurrentSignatureHelpParameterCount(expectedCount: number) {
|
||||
this.taoInvalidReason = 'verifyCurrentSignatureHelpParameterCount NYI';
|
||||
|
||||
assert.equal(this.getActiveSignatureHelp().parameters.length, expectedCount);
|
||||
assert.equal(this.getActiveSignatureHelpItem().parameters.length, expectedCount);
|
||||
}
|
||||
|
||||
public verifyCurrentSignatureHelpTypeParameterCount(expectedCount: number) {
|
||||
this.taoInvalidReason = 'verifyCurrentSignatureHelpTypeParameterCount NYI';
|
||||
|
||||
// assert.equal(this.getActiveSignatureHelp().typeParameters.length, expectedCount);
|
||||
// assert.equal(this.getActiveSignatureHelpItem().typeParameters.length, expectedCount);
|
||||
}
|
||||
|
||||
public verifyCurrentSignatureHelpDocComment(docComment: string) {
|
||||
this.taoInvalidReason = 'verifyCurrentSignatureHelpDocComment NYI';
|
||||
|
||||
var actualDocComment = this.getActiveSignatureHelp().documentation;
|
||||
var actualDocComment = this.getActiveSignatureHelpItem().documentation;
|
||||
assert.equal(actualDocComment, docComment);
|
||||
}
|
||||
|
||||
@@ -941,7 +941,7 @@ module FourSlash {
|
||||
// return help.formal;
|
||||
//}
|
||||
|
||||
private getActiveSignatureHelp() {
|
||||
private getActiveSignatureHelpItem() {
|
||||
var help = this.languageService.getSignatureHelpItems(this.activeFile.fileName, this.currentCaretPosition);
|
||||
|
||||
// If the signature hasn't been narrowed down yet (e.g. no parameters have yet been entered),
|
||||
@@ -953,14 +953,13 @@ module FourSlash {
|
||||
}
|
||||
|
||||
private getActiveParameter(): ts.SignatureHelpParameter {
|
||||
var currentSig = this.getActiveSignatureHelp();
|
||||
var help = this.languageService.getSignatureHelpItems(this.activeFile.fileName, this.currentCaretPosition);
|
||||
|
||||
var item = help.items[help.selectedItemIndex];
|
||||
var state = this.languageService.getSignatureHelpCurrentArgumentState(this.activeFile.fileName, this.currentCaretPosition, help.applicableSpan.start());
|
||||
|
||||
// Same logic as in getActiveSignatureHelp - this value might be -1 until a parameter value actually gets typed
|
||||
var currentParam = state === null ? 0 : state.argumentIndex;
|
||||
var currentParam = state === undefined ? 0 : state.argumentIndex;
|
||||
return item.parameters[currentParam];
|
||||
}
|
||||
|
||||
@@ -1083,7 +1082,7 @@ module FourSlash {
|
||||
}
|
||||
|
||||
public printCurrentSignatureHelp() {
|
||||
var sigHelp = this.getActiveSignatureHelp();
|
||||
var sigHelp = this.getActiveSignatureHelpItem();
|
||||
Harness.IO.log(JSON.stringify(sigHelp));
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
module ts.formatting {
|
||||
export module SmartIndenter {
|
||||
|
||||
export function getIndentation(position: number, sourceFile: SourceFile, options: TypeScript.FormattingOptions): number {
|
||||
if (position > sourceFile.text.length) {
|
||||
return 0; // past EOF
|
||||
@@ -108,8 +107,10 @@ module ts.formatting {
|
||||
*/
|
||||
function getActualIndentationForListItemBeforeComma(commaToken: Node, sourceFile: SourceFile, options: TypeScript.FormattingOptions): number {
|
||||
// previous token is comma that separates items in list - find the previous item and try to derive indentation from it
|
||||
var itemInfo = findPrecedingListItem(commaToken);
|
||||
return deriveActualIndentationFromList(itemInfo.list.getChildren(), itemInfo.listItemIndex, sourceFile, options);
|
||||
var commaItemInfo = findListItemInfo(commaToken);
|
||||
Debug.assert(commaItemInfo.listItemIndex > 0);
|
||||
// The item we're interested in is right before the comma
|
||||
return deriveActualIndentationFromList(commaItemInfo.list.getChildren(), commaItemInfo.listItemIndex - 1, sourceFile, options);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -167,27 +168,6 @@ module ts.formatting {
|
||||
return sourceFile.getLineAndCharacterFromPosition(n.getStart(sourceFile));
|
||||
}
|
||||
|
||||
function findPrecedingListItem(commaToken: Node): { listItemIndex: number; list: Node } {
|
||||
// CommaToken node is synthetic and thus will be stored in SyntaxList, however parent of the CommaToken points to the container of the SyntaxList skipping the list.
|
||||
// In order to find the preceding list item we first need to locate SyntaxList itself and then search for the position of CommaToken
|
||||
var syntaxList = forEach(commaToken.parent.getChildren(), c => {
|
||||
// find syntax list that covers the span of CommaToken
|
||||
if (c.kind == SyntaxKind.SyntaxList && c.pos <= commaToken.end && c.end >= commaToken.end) {
|
||||
return c;
|
||||
}
|
||||
});
|
||||
Debug.assert(syntaxList);
|
||||
|
||||
var children = syntaxList.getChildren();
|
||||
var commaIndex = indexOf(children, commaToken);
|
||||
Debug.assert(commaIndex !== -1 && commaIndex !== 0);
|
||||
|
||||
return {
|
||||
listItemIndex: commaIndex - 1,
|
||||
list: syntaxList
|
||||
};
|
||||
}
|
||||
|
||||
function positionBelongsToNode(candidate: Node, position: number, sourceFile: SourceFile): boolean {
|
||||
return candidate.end > position || !isCompletedNode(candidate, sourceFile);
|
||||
}
|
||||
@@ -288,112 +268,6 @@ module ts.formatting {
|
||||
return column;
|
||||
}
|
||||
|
||||
function findNextToken(previousToken: Node, parent: Node): Node {
|
||||
return find(parent);
|
||||
|
||||
function find(n: Node): Node {
|
||||
if (isToken(n) && n.pos === previousToken.end) {
|
||||
// this is token that starts at the end of previous token - return it
|
||||
return n;
|
||||
}
|
||||
|
||||
var children = n.getChildren();
|
||||
for (var i = 0, len = children.length; i < len; ++i) {
|
||||
var child = children[i];
|
||||
var shouldDiveInChildNode =
|
||||
// previous token is enclosed somewhere in the child
|
||||
(child.pos <= previousToken.pos && child.end > previousToken.end) ||
|
||||
// previous token ends exactly at the beginning of child
|
||||
(child.pos === previousToken.end);
|
||||
|
||||
if (shouldDiveInChildNode && nodeHasTokens(child)) {
|
||||
return find(child);
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function findPrecedingToken(position: number, sourceFile: SourceFile): Node {
|
||||
return find(sourceFile);
|
||||
|
||||
function findRightmostToken(n: Node): Node {
|
||||
if (isToken(n)) {
|
||||
return n;
|
||||
}
|
||||
|
||||
var children = n.getChildren();
|
||||
var candidate = findRightmostChildNodeWithTokens(children, /*exclusiveStartPosition*/ children.length);
|
||||
return candidate && findRightmostToken(candidate);
|
||||
|
||||
}
|
||||
|
||||
function find(n: Node): Node {
|
||||
if (isToken(n)) {
|
||||
return n;
|
||||
}
|
||||
|
||||
var children = n.getChildren();
|
||||
for (var i = 0, len = children.length; i < len; ++i) {
|
||||
var child = children[i];
|
||||
if (nodeHasTokens(child)) {
|
||||
if (position < child.end) {
|
||||
if (child.getStart(sourceFile) >= position) {
|
||||
// actual start of the node is past the position - previous token should be at the end of previous child
|
||||
var candidate = findRightmostChildNodeWithTokens(children, /*exclusiveStartPosition*/ i);
|
||||
return candidate && findRightmostToken(candidate)
|
||||
}
|
||||
else {
|
||||
// candidate should be in this node
|
||||
return find(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Debug.assert(n.kind === SyntaxKind.SourceFile);
|
||||
|
||||
// Here we know that none of child token nodes embrace the position,
|
||||
// the only known case is when position is at the end of the file.
|
||||
// Try to find the rightmost token in the file without filtering.
|
||||
// Namely we are skipping the check: 'position < node.end'
|
||||
if (children.length) {
|
||||
var candidate = findRightmostChildNodeWithTokens(children, /*exclusiveStartPosition*/ children.length);
|
||||
return candidate && findRightmostToken(candidate);
|
||||
}
|
||||
}
|
||||
|
||||
/// finds last node that is considered as candidate for search (isCandidate(node) === true) starting from 'exclusiveStartPosition'
|
||||
function findRightmostChildNodeWithTokens(children: Node[], exclusiveStartPosition: number): Node {
|
||||
for (var i = exclusiveStartPosition - 1; i >= 0; --i) {
|
||||
if (nodeHasTokens(children[i])) {
|
||||
return children[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks if node is something that can contain tokens (except EOF) - filters out EOF tokens, Missing\Omitted expressions, empty SyntaxLists and expression statements that wrap any of listed nodes.
|
||||
*/
|
||||
function nodeHasTokens(n: Node): boolean {
|
||||
if (n.kind === SyntaxKind.ExpressionStatement) {
|
||||
return nodeHasTokens((<ExpressionStatement>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 isToken(n: Node): boolean {
|
||||
return n.kind >= SyntaxKind.FirstToken && n.kind <= SyntaxKind.LastToken;
|
||||
}
|
||||
|
||||
function nodeContentIsIndented(parent: Node, child: Node): boolean {
|
||||
switch (parent.kind) {
|
||||
case SyntaxKind.ClassDeclaration:
|
||||
|
||||
+32
-41
@@ -9,6 +9,8 @@
|
||||
/// <reference path='getScriptLexicalStructureWalker.ts' />
|
||||
/// <reference path='breakpoints.ts' />
|
||||
/// <reference path='indentation.ts' />
|
||||
/// <reference path='signatureHelp.ts' />
|
||||
/// <reference path='utilities.ts' />
|
||||
/// <reference path='formatting\formatting.ts' />
|
||||
/// <reference path='formatting\smartIndenter.ts' />
|
||||
|
||||
@@ -201,7 +203,7 @@ module ts {
|
||||
}
|
||||
|
||||
public getFirstToken(sourceFile?: SourceFile): Node {
|
||||
var children = this.getChildren(sourceFile);
|
||||
var children = this.getChildren();
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
var child = children[i];
|
||||
if (child.kind < SyntaxKind.Missing) return child;
|
||||
@@ -1129,7 +1131,7 @@ module ts {
|
||||
|
||||
export class OperationCanceledException { }
|
||||
|
||||
class CancellationTokenObject {
|
||||
export class CancellationTokenObject {
|
||||
|
||||
public static None: CancellationTokenObject = new CancellationTokenObject(null)
|
||||
|
||||
@@ -2250,40 +2252,6 @@ module ts {
|
||||
}
|
||||
}
|
||||
|
||||
/** Get the token whose text contains the position, or the containing node. */
|
||||
function getNodeAtPosition(sourceFile: SourceFile, position: number) {
|
||||
var current: Node = sourceFile;
|
||||
outer: while (true) {
|
||||
// find the child that has this
|
||||
for (var i = 0, n = current.getChildCount(); i < n; i++) {
|
||||
var child = current.getChildAt(i);
|
||||
if (child.getStart() <= position && position < child.getEnd()) {
|
||||
current = child;
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
return current;
|
||||
}
|
||||
}
|
||||
|
||||
/** Get a token that contains the position. This is guaranteed to return a token, the position can be in the
|
||||
* leading trivia or within the token text.
|
||||
*/
|
||||
function getTokenAtPosition(sourceFile: SourceFile, position: number) {
|
||||
var current: Node = sourceFile;
|
||||
outer: while (true) {
|
||||
// find the child that has this
|
||||
for (var i = 0, n = current.getChildCount(); i < n; i++) {
|
||||
var child = current.getChildAt(i);
|
||||
if (child.getFullStart() <= position && position < child.getEnd()) {
|
||||
current = child;
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
return current;
|
||||
}
|
||||
}
|
||||
|
||||
function getContainerNode(node: Node): Node {
|
||||
while (true) {
|
||||
node = node.parent;
|
||||
@@ -2546,7 +2514,7 @@ module ts {
|
||||
result.push(getDefinitionInfo(declarations[declarations.length - 1], symbolKind, symbolName, containerName));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -2747,7 +2715,7 @@ module ts {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (shouldHighlightNextKeyword) {
|
||||
result.push(new ReferenceEntry(filename, TypeScript.TextSpan.fromBounds(elseKeyword.getStart(), ifKeyword.end), /* isWriteAccess */ false));
|
||||
i++; // skip the next keyword
|
||||
@@ -3754,7 +3722,30 @@ module ts {
|
||||
|
||||
// Reset writer back to undefined to make sure that we produce an error message if CompilerHost.writeFile method is called when we are not in getEmitOutput
|
||||
writer = undefined;
|
||||
return emitOutput;
|
||||
return emitOutput;
|
||||
}
|
||||
|
||||
// Signature help
|
||||
/**
|
||||
* This is a semantic operation.
|
||||
*/
|
||||
function getSignatureHelpItems(fileName: string, position: number): SignatureHelpItems {
|
||||
synchronizeHostData();
|
||||
|
||||
fileName = TypeScript.switchToForwardSlashes(fileName);
|
||||
var sourceFile = getSourceFile(fileName);
|
||||
|
||||
return SignatureHelp.getSignatureHelpItems(sourceFile, position, typeInfoResolver, cancellationToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a syntactic operation
|
||||
*/
|
||||
function getSignatureHelpCurrentArgumentState(fileName: string, position: number, applicableSpanStart: number): SignatureHelpState {
|
||||
fileName = TypeScript.switchToForwardSlashes(fileName);
|
||||
var sourceFile = getCurrentSourceFile(fileName);
|
||||
|
||||
return SignatureHelp.getSignatureHelpCurrentArgumentState(sourceFile, position, applicableSpanStart);
|
||||
}
|
||||
|
||||
/// Syntactic features
|
||||
@@ -4353,9 +4344,9 @@ module ts {
|
||||
getCompletionsAtPosition: getCompletionsAtPosition,
|
||||
getCompletionEntryDetails: getCompletionEntryDetails,
|
||||
getTypeAtPosition: getTypeAtPosition,
|
||||
getSignatureHelpItems: getSignatureHelpItems,
|
||||
getSignatureHelpCurrentArgumentState: getSignatureHelpCurrentArgumentState,
|
||||
getQuickInfoAtPosition: getQuickInfoAtPosition,
|
||||
getSignatureHelpItems: (filename, position): SignatureHelpItems => null,
|
||||
getSignatureHelpCurrentArgumentState: (fileName, position, applicableSpanStart): SignatureHelpState => null,
|
||||
getDefinitionAtPosition: getDefinitionAtPosition,
|
||||
getReferencesAtPosition: getReferencesAtPosition,
|
||||
getOccurrencesAtPosition: getOccurrencesAtPosition,
|
||||
|
||||
@@ -599,8 +599,8 @@ module ts {
|
||||
return this.forwardJSONCall(
|
||||
"getSignatureHelpCurrentArgumentState('" + fileName + "', " + position + ", " + applicableSpanStart + ")",
|
||||
() => {
|
||||
var signatureInfo = this.languageService.getSignatureHelpItems(fileName, position);
|
||||
return signatureInfo;
|
||||
var signatureHelpState = this.languageService.getSignatureHelpCurrentArgumentState(fileName, position, applicableSpanStart);
|
||||
return signatureHelpState;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,349 @@
|
||||
///<reference path='services.ts' />
|
||||
|
||||
module ts.SignatureHelp {
|
||||
|
||||
// A partially written generic type expression is not guaranteed to have the correct syntax tree. the expression could be parsed as less than/greater than expression or a comma expression
|
||||
// or some other combination depending on what the user has typed so far. For the purposes of signature help we need to consider any location after "<" as a possible generic type reference.
|
||||
// To do this, the method will back parse the expression starting at the position required. it will try to parse the current expression as a generic type expression, if it did succeed it
|
||||
// will return the generic identifier that started the expression (e.g. "foo" in "foo<any, |"). It is then up to the caller to ensure that this is a valid generic expression through
|
||||
// looking up the type. The method will also keep track of the parameter index inside the expression.
|
||||
//public static isInPartiallyWrittenTypeArgumentList(syntaxTree: TypeScript.SyntaxTree, position: number): any {
|
||||
// var token = Syntax.findTokenOnLeft(syntaxTree.sourceUnit(), position, /*includeSkippedTokens*/ true);
|
||||
|
||||
// if (token && TypeScript.Syntax.hasAncestorOfKind(token, TypeScript.SyntaxKind.TypeParameterList)) {
|
||||
// // We are in the wrong generic list. bail out
|
||||
// return null;
|
||||
// }
|
||||
|
||||
// var stack = 0;
|
||||
// var argumentIndex = 0;
|
||||
|
||||
// whileLoop:
|
||||
// while (token) {
|
||||
// switch (token.kind()) {
|
||||
// case TypeScript.SyntaxKind.LessThanToken:
|
||||
// if (stack === 0) {
|
||||
// // Found the beginning of the generic argument expression
|
||||
// var lessThanToken = token;
|
||||
// token = previousToken(token, /*includeSkippedTokens*/ true);
|
||||
// if (!token || token.kind() !== TypeScript.SyntaxKind.IdentifierName) {
|
||||
// break whileLoop;
|
||||
// }
|
||||
|
||||
// // Found the name, return the data
|
||||
// return {
|
||||
// genericIdentifer: token,
|
||||
// lessThanToken: lessThanToken,
|
||||
// argumentIndex: argumentIndex
|
||||
// };
|
||||
// }
|
||||
// else if (stack < 0) {
|
||||
// // Seen one too many less than tokens, bail out
|
||||
// break whileLoop;
|
||||
// }
|
||||
// else {
|
||||
// stack--;
|
||||
// }
|
||||
|
||||
// break;
|
||||
|
||||
// case TypeScript.SyntaxKind.GreaterThanGreaterThanGreaterThanToken:
|
||||
// stack++;
|
||||
|
||||
// // Intentaion fall through
|
||||
// case TypeScript.SyntaxKind.GreaterThanToken:
|
||||
// stack++;
|
||||
// break;
|
||||
|
||||
// case TypeScript.SyntaxKind.CommaToken:
|
||||
// if (stack == 0) {
|
||||
// argumentIndex++;
|
||||
// }
|
||||
|
||||
// break;
|
||||
|
||||
// case TypeScript.SyntaxKind.CloseBraceToken:
|
||||
// // This can be object type, skip untill we find the matching open brace token
|
||||
// var unmatchedOpenBraceTokens = 0;
|
||||
|
||||
// // Skip untill the matching open brace token
|
||||
// token = SignatureInfoHelpers.moveBackUpTillMatchingTokenKind(token, TypeScript.SyntaxKind.CloseBraceToken, TypeScript.SyntaxKind.OpenBraceToken);
|
||||
// if (!token) {
|
||||
// // No matching token was found. bail out
|
||||
// break whileLoop;
|
||||
// }
|
||||
|
||||
// break;
|
||||
|
||||
// case TypeScript.SyntaxKind.EqualsGreaterThanToken:
|
||||
// // This can be a function type or a constructor type. In either case, we want to skip the function defintion
|
||||
// token = previousToken(token, /*includeSkippedTokens*/ true);
|
||||
|
||||
// if (token && token.kind() === TypeScript.SyntaxKind.CloseParenToken) {
|
||||
// // Skip untill the matching open paren token
|
||||
// token = SignatureInfoHelpers.moveBackUpTillMatchingTokenKind(token, TypeScript.SyntaxKind.CloseParenToken, TypeScript.SyntaxKind.OpenParenToken);
|
||||
|
||||
// if (token && token.kind() === TypeScript.SyntaxKind.GreaterThanToken) {
|
||||
// // Another generic type argument list, skip it\
|
||||
// token = SignatureInfoHelpers.moveBackUpTillMatchingTokenKind(token, TypeScript.SyntaxKind.GreaterThanToken, TypeScript.SyntaxKind.LessThanToken);
|
||||
// }
|
||||
|
||||
// if (token && token.kind() === TypeScript.SyntaxKind.NewKeyword) {
|
||||
// // In case this was a constructor type, skip the new keyword
|
||||
// token = previousToken(token, /*includeSkippedTokens*/ true);
|
||||
// }
|
||||
|
||||
// if (!token) {
|
||||
// // No matching token was found. bail out
|
||||
// break whileLoop;
|
||||
// }
|
||||
// }
|
||||
// else {
|
||||
// // This is not a funtion type. exit the main loop
|
||||
// break whileLoop;
|
||||
// }
|
||||
|
||||
// break;
|
||||
|
||||
// case TypeScript.SyntaxKind.IdentifierName:
|
||||
// case TypeScript.SyntaxKind.AnyKeyword:
|
||||
// case TypeScript.SyntaxKind.NumberKeyword:
|
||||
// case TypeScript.SyntaxKind.StringKeyword:
|
||||
// case TypeScript.SyntaxKind.VoidKeyword:
|
||||
// case TypeScript.SyntaxKind.BooleanKeyword:
|
||||
// case TypeScript.SyntaxKind.DotToken:
|
||||
// case TypeScript.SyntaxKind.OpenBracketToken:
|
||||
// case TypeScript.SyntaxKind.CloseBracketToken:
|
||||
// // Valid tokens in a type name. Skip.
|
||||
// break;
|
||||
|
||||
// default:
|
||||
// break whileLoop;
|
||||
// }
|
||||
|
||||
// token = previousToken(token, /*includeSkippedTokens*/ true);
|
||||
// }
|
||||
|
||||
// return null;
|
||||
//}
|
||||
|
||||
//private static moveBackUpTillMatchingTokenKind(token: TypeScript.ISyntaxToken, tokenKind: TypeScript.SyntaxKind, matchingTokenKind: TypeScript.SyntaxKind): TypeScript.ISyntaxToken {
|
||||
// if (!token || token.kind() !== tokenKind) {
|
||||
// throw TypeScript.Errors.invalidOperation();
|
||||
// }
|
||||
|
||||
// // Skip the current token
|
||||
// token = previousToken(token, /*includeSkippedTokens*/ true);
|
||||
|
||||
// var stack = 0;
|
||||
|
||||
// while (token) {
|
||||
// if (token.kind() === matchingTokenKind) {
|
||||
// if (stack === 0) {
|
||||
// // Found the matching token, return
|
||||
// return token;
|
||||
// }
|
||||
// else if (stack < 0) {
|
||||
// // tokens overlapped.. bail out.
|
||||
// break;
|
||||
// }
|
||||
// else {
|
||||
// stack--;
|
||||
// }
|
||||
// }
|
||||
// else if (token.kind() === tokenKind) {
|
||||
// stack++;
|
||||
// }
|
||||
|
||||
// // Move back
|
||||
// token = previousToken(token, /*includeSkippedTokens*/ true);
|
||||
// }
|
||||
|
||||
// // Did not find matching token
|
||||
// return null;
|
||||
//}
|
||||
var emptyArray: any[] = [];
|
||||
|
||||
export function getSignatureHelpItems(sourceFile: SourceFile, position: number, typeInfoResolver: TypeChecker, cancellationToken: CancellationTokenObject): SignatureHelpItems {
|
||||
// Decide whether to show signature help
|
||||
var startingToken = findTokenOnLeftOfPosition(sourceFile, position);
|
||||
if (!startingToken) {
|
||||
// We are at the beginning of the file
|
||||
return undefined;
|
||||
}
|
||||
|
||||
var argumentList = getContainingArgumentList(startingToken);
|
||||
cancellationToken.throwIfCancellationRequested();
|
||||
|
||||
// Semantic filtering of signature help
|
||||
if (!argumentList) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
var call = <CallExpression>argumentList.parent;
|
||||
var candidates = <Signature[]>[];
|
||||
var resolvedSignature = typeInfoResolver.getResolvedSignature(call, candidates);
|
||||
cancellationToken.throwIfCancellationRequested();
|
||||
|
||||
if (!candidates.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return createSignatureHelpItems(candidates, resolvedSignature, argumentList);
|
||||
|
||||
/**
|
||||
* If node is an argument, returns its index in the argument list.
|
||||
* If not, returns -1.
|
||||
*/
|
||||
function getImmediatelyContainingArgumentList(node: Node): Node {
|
||||
if (node.parent.kind !== SyntaxKind.CallExpression && node.parent.kind !== SyntaxKind.NewExpression) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// There are 3 cases to handle:
|
||||
// 1. The token introduces a list, and should begin a sig help session
|
||||
// 2. The token is either not associated with a list, or ends a list, so the session should end
|
||||
// 3. The token is buried inside a list, and should give sig help
|
||||
//
|
||||
// The following are examples of each:
|
||||
//
|
||||
// Case 1:
|
||||
// foo<$T, U>($a, b) -> The token introduces a list, and should begin a sig help session
|
||||
// Case 2:
|
||||
// fo$o<T, U>$(a, b)$ -> The token is either not associated with a list, or ends a list, so the session should end
|
||||
// Case 3:
|
||||
// foo<T$, U$>(a$, $b$) -> The token is buried inside a list, and should give sig help
|
||||
var parent = <CallExpression>node.parent;
|
||||
// Find out if 'node' is an argument, a type argument, or neither
|
||||
if (node.kind === SyntaxKind.LessThanToken || node.kind === SyntaxKind.OpenParenToken) {
|
||||
// Find the list that starts right *after* the < or ( token
|
||||
var list = getChildListThatStartsWithOpenerToken(parent, node, sourceFile);
|
||||
Debug.assert(list);
|
||||
return list;
|
||||
}
|
||||
|
||||
if (node.kind === SyntaxKind.GreaterThanToken
|
||||
|| node.kind === SyntaxKind.CloseParenToken
|
||||
|| node === parent.func) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return findContainingList(node);
|
||||
}
|
||||
|
||||
function getContainingArgumentList(node: Node): Node {
|
||||
for (var n = node; n.kind !== SyntaxKind.SourceFile; n = n.parent) {
|
||||
if (n.kind === SyntaxKind.FunctionBlock) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
var argumentList = getImmediatelyContainingArgumentList(n);
|
||||
if (argumentList) {
|
||||
return argumentList;
|
||||
}
|
||||
|
||||
|
||||
// TODO: Handle generic call with incomplete syntax
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function createSignatureHelpItems(candidates: Signature[], bestSignature: Signature, argumentListOrTypeArgumentList: Node): SignatureHelpItems {
|
||||
var items = map(candidates, candidateSignature => {
|
||||
var parameters = candidateSignature.parameters;
|
||||
var parameterHelpItems = parameters.length === 0 ? emptyArray : map(parameters, p => {
|
||||
var display = p.name;
|
||||
if (candidateSignature.hasRestParameter && parameters[parameters.length - 1] === p) {
|
||||
display = "..." + display;
|
||||
}
|
||||
var isOptional = !!(p.valueDeclaration.flags & NodeFlags.QuestionMark);
|
||||
if (isOptional) {
|
||||
display += "?";
|
||||
}
|
||||
display += ": " + typeInfoResolver.typeToString(typeInfoResolver.getTypeOfSymbol(p), argumentListOrTypeArgumentList);
|
||||
return new SignatureHelpParameter(p.name, "", display, isOptional);
|
||||
});
|
||||
var callTargetNode = (<CallExpression>argumentListOrTypeArgumentList.parent).func;
|
||||
var callTargetSymbol = typeInfoResolver.getSymbolInfo(callTargetNode);
|
||||
var signatureName = callTargetSymbol ? typeInfoResolver.symbolToString(callTargetSymbol, /*enclosingDeclaration*/ undefined, /*meaning*/ undefined) : "";
|
||||
var prefix = signatureName;
|
||||
// TODO(jfreeman): Constraints?
|
||||
if (candidateSignature.typeParameters && candidateSignature.typeParameters.length) {
|
||||
prefix += "<" + map(candidateSignature.typeParameters, tp => tp.symbol.name).join(", ") + ">";
|
||||
}
|
||||
prefix += "(";
|
||||
var suffix = "): " + typeInfoResolver.typeToString(candidateSignature.getReturnType(), argumentListOrTypeArgumentList);
|
||||
return new SignatureHelpItem(candidateSignature.hasRestParameter, prefix, suffix, ", ", parameterHelpItems, "");
|
||||
});
|
||||
var selectedItemIndex = candidates.indexOf(bestSignature);
|
||||
if (selectedItemIndex < 0) {
|
||||
selectedItemIndex = 0;
|
||||
}
|
||||
|
||||
// We use full start and skip trivia on the end because we want to include trivia on
|
||||
// both sides. For example,
|
||||
//
|
||||
// foo( /*comment */ a, b, c /*comment*/ )
|
||||
// | |
|
||||
//
|
||||
// The applicable span is from the first bar to the second bar (inclusive,
|
||||
// but not including parentheses)
|
||||
var applicableSpanStart = argumentListOrTypeArgumentList.getFullStart();
|
||||
var applicableSpanEnd = skipTrivia(sourceFile.text, argumentListOrTypeArgumentList.end, /*stopAfterLineBreak*/ false);
|
||||
var applicableSpan = new TypeScript.TextSpan(applicableSpanStart, applicableSpanEnd - applicableSpanStart);
|
||||
return new SignatureHelpItems(items, applicableSpan, selectedItemIndex);
|
||||
}
|
||||
}
|
||||
|
||||
export function getSignatureHelpCurrentArgumentState(sourceFile: SourceFile, position: number, applicableSpanStart: number): SignatureHelpState {
|
||||
var tokenPrecedingSpanStart = findPrecedingToken(applicableSpanStart, sourceFile);
|
||||
if (!tokenPrecedingSpanStart) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (tokenPrecedingSpanStart.kind !== SyntaxKind.OpenParenToken && tokenPrecedingSpanStart.kind !== SyntaxKind.LessThanToken) {
|
||||
// The span start must have moved backward in the file (for example if the open paren was backspaced)
|
||||
return undefined;
|
||||
}
|
||||
|
||||
var tokenPrecedingCurrentPosition = findPrecedingToken(position, sourceFile);
|
||||
var call = <CallExpression>tokenPrecedingSpanStart.parent;
|
||||
Debug.assert(call.kind === SyntaxKind.CallExpression || call.kind === SyntaxKind.NewExpression, "wrong call kind " + SyntaxKind[call.kind]);
|
||||
if (tokenPrecedingCurrentPosition.kind === SyntaxKind.CloseParenToken || tokenPrecedingCurrentPosition.kind === SyntaxKind.GreaterThanToken) {
|
||||
if (tokenPrecedingCurrentPosition.parent === call) {
|
||||
// This call expression is complete. Stop signature help.
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
var argumentListOrTypeArgumentList = getChildListThatStartsWithOpenerToken(call, tokenPrecedingSpanStart, sourceFile);
|
||||
// Debug.assert(argumentListOrTypeArgumentList.getChildCount() === 0 || argumentListOrTypeArgumentList.getChildCount() % 2 === 1, "Even number of children");
|
||||
|
||||
// The call might be finished, but incorrectly. Check if we are still within the bounds of the call
|
||||
if (position > skipTrivia(sourceFile.text, argumentListOrTypeArgumentList.end, /*stopAfterLineBreak*/ false)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
var numberOfCommas = countWhere(argumentListOrTypeArgumentList.getChildren(), arg => arg.kind === SyntaxKind.CommaToken);
|
||||
var argumentCount = numberOfCommas + 1;
|
||||
if (argumentCount <= 1) {
|
||||
return new SignatureHelpState(/*argumentIndex*/ 0, argumentCount);
|
||||
}
|
||||
|
||||
var indexOfNodeContainingPosition = findListItemIndexContainingPosition(argumentListOrTypeArgumentList, position);
|
||||
|
||||
// indexOfNodeContainingPosition checks that position is between pos and end of each child, so it is
|
||||
// possible that we are to the right of all children. Assume that we are still within
|
||||
// the applicable span and that we are typing the last argument
|
||||
// Alternatively, we could be in range of one of the arguments, in which case we need to divide
|
||||
// by 2 to exclude commas. Use bit shifting in order to take the floor of the division.
|
||||
var argumentIndex = indexOfNodeContainingPosition < 0 ? argumentCount - 1 : indexOfNodeContainingPosition >> 1;
|
||||
return new SignatureHelpState(argumentIndex, argumentCount);
|
||||
}
|
||||
|
||||
function getChildListThatStartsWithOpenerToken(parent: Node, openerToken: Node, sourceFile: SourceFile): Node {
|
||||
var children = parent.getChildren(sourceFile);
|
||||
var indexOfOpenerToken = children.indexOf(openerToken);
|
||||
return children[indexOfOpenerToken + 1];
|
||||
}
|
||||
}
|
||||
@@ -1,346 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved. Licensed under the Apache License, Version 2.0.
|
||||
// See LICENSE.txt in the project root for complete license information.
|
||||
|
||||
///<reference path='references.ts' />
|
||||
|
||||
module TypeScript.Services {
|
||||
|
||||
export interface IPartiallyWrittenTypeArgumentListInformation {
|
||||
genericIdentifer: TypeScript.ISyntaxToken;
|
||||
lessThanToken: TypeScript.ISyntaxToken;
|
||||
argumentIndex: number;
|
||||
}
|
||||
|
||||
export interface IExpressionWithArgumentListSyntax extends IExpressionSyntax {
|
||||
expression: IExpressionSyntax;
|
||||
argumentList: ArgumentListSyntax;
|
||||
}
|
||||
|
||||
export class SignatureInfoHelpers {
|
||||
|
||||
// A partially written generic type expression is not guaranteed to have the correct syntax tree. the expression could be parsed as less than/greater than expression or a comma expression
|
||||
// or some other combination depending on what the user has typed so far. For the purposes of signature help we need to consider any location after "<" as a possible generic type reference.
|
||||
// To do this, the method will back parse the expression starting at the position required. it will try to parse the current expression as a generic type expression, if it did succeed it
|
||||
// will return the generic identifier that started the expression (e.g. "foo" in "foo<any, |"). It is then up to the caller to ensure that this is a valid generic expression through
|
||||
// looking up the type. The method will also keep track of the parameter index inside the expression.
|
||||
public static isInPartiallyWrittenTypeArgumentList(syntaxTree: TypeScript.SyntaxTree, position: number): IPartiallyWrittenTypeArgumentListInformation {
|
||||
var token = Syntax.findTokenOnLeft(syntaxTree.sourceUnit(), position, /*includeSkippedTokens*/ true);
|
||||
|
||||
if (token && TypeScript.Syntax.hasAncestorOfKind(token, TypeScript.SyntaxKind.TypeParameterList)) {
|
||||
// We are in the wrong generic list. bail out
|
||||
return null;
|
||||
}
|
||||
|
||||
var stack = 0;
|
||||
var argumentIndex = 0;
|
||||
|
||||
whileLoop:
|
||||
while (token) {
|
||||
switch (token.kind()) {
|
||||
case TypeScript.SyntaxKind.LessThanToken:
|
||||
if (stack === 0) {
|
||||
// Found the beginning of the generic argument expression
|
||||
var lessThanToken = token;
|
||||
token = previousToken(token, /*includeSkippedTokens*/ true);
|
||||
if (!token || token.kind() !== TypeScript.SyntaxKind.IdentifierName) {
|
||||
break whileLoop;
|
||||
}
|
||||
|
||||
// Found the name, return the data
|
||||
return {
|
||||
genericIdentifer: token,
|
||||
lessThanToken: lessThanToken,
|
||||
argumentIndex: argumentIndex
|
||||
};
|
||||
}
|
||||
else if (stack < 0) {
|
||||
// Seen one too many less than tokens, bail out
|
||||
break whileLoop;
|
||||
}
|
||||
else {
|
||||
stack--;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case TypeScript.SyntaxKind.GreaterThanGreaterThanGreaterThanToken:
|
||||
stack++;
|
||||
|
||||
// Intentaion fall through
|
||||
case TypeScript.SyntaxKind.GreaterThanToken:
|
||||
stack++;
|
||||
break;
|
||||
|
||||
case TypeScript.SyntaxKind.CommaToken:
|
||||
if (stack == 0) {
|
||||
argumentIndex++;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case TypeScript.SyntaxKind.CloseBraceToken:
|
||||
// This can be object type, skip untill we find the matching open brace token
|
||||
var unmatchedOpenBraceTokens = 0;
|
||||
|
||||
// Skip untill the matching open brace token
|
||||
token = SignatureInfoHelpers.moveBackUpTillMatchingTokenKind(token, TypeScript.SyntaxKind.CloseBraceToken, TypeScript.SyntaxKind.OpenBraceToken);
|
||||
if (!token) {
|
||||
// No matching token was found. bail out
|
||||
break whileLoop;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case TypeScript.SyntaxKind.EqualsGreaterThanToken:
|
||||
// This can be a function type or a constructor type. In either case, we want to skip the function defintion
|
||||
token = previousToken(token, /*includeSkippedTokens*/ true);
|
||||
|
||||
if (token && token.kind() === TypeScript.SyntaxKind.CloseParenToken) {
|
||||
// Skip untill the matching open paren token
|
||||
token = SignatureInfoHelpers.moveBackUpTillMatchingTokenKind(token, TypeScript.SyntaxKind.CloseParenToken, TypeScript.SyntaxKind.OpenParenToken);
|
||||
|
||||
if (token && token.kind() === TypeScript.SyntaxKind.GreaterThanToken) {
|
||||
// Another generic type argument list, skip it\
|
||||
token = SignatureInfoHelpers.moveBackUpTillMatchingTokenKind(token, TypeScript.SyntaxKind.GreaterThanToken, TypeScript.SyntaxKind.LessThanToken);
|
||||
}
|
||||
|
||||
if (token && token.kind() === TypeScript.SyntaxKind.NewKeyword) {
|
||||
// In case this was a constructor type, skip the new keyword
|
||||
token = previousToken(token, /*includeSkippedTokens*/ true);
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
// No matching token was found. bail out
|
||||
break whileLoop;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// This is not a funtion type. exit the main loop
|
||||
break whileLoop;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case TypeScript.SyntaxKind.IdentifierName:
|
||||
case TypeScript.SyntaxKind.AnyKeyword:
|
||||
case TypeScript.SyntaxKind.NumberKeyword:
|
||||
case TypeScript.SyntaxKind.StringKeyword:
|
||||
case TypeScript.SyntaxKind.VoidKeyword:
|
||||
case TypeScript.SyntaxKind.BooleanKeyword:
|
||||
case TypeScript.SyntaxKind.DotToken:
|
||||
case TypeScript.SyntaxKind.OpenBracketToken:
|
||||
case TypeScript.SyntaxKind.CloseBracketToken:
|
||||
// Valid tokens in a type name. Skip.
|
||||
break;
|
||||
|
||||
default:
|
||||
break whileLoop;
|
||||
}
|
||||
|
||||
token = previousToken(token, /*includeSkippedTokens*/ true);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static getSignatureInfoFromSignatureSymbol(symbol: TypeScript.PullSymbol, signatures: TypeScript.PullSignatureSymbol[], enclosingScopeSymbol: TypeScript.PullSymbol, compilerState: LanguageServiceCompiler) {
|
||||
var signatureGroup: FormalSignatureItemInfo[] = [];
|
||||
|
||||
var hasOverloads = signatures.length > 1;
|
||||
|
||||
for (var i = 0, n = signatures.length; i < n; i++) {
|
||||
var signature = signatures[i];
|
||||
|
||||
// filter out the definition signature if there are overloads
|
||||
if (hasOverloads && signature.isDefinition()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var signatureGroupInfo = new FormalSignatureItemInfo();
|
||||
var paramIndexInfo: number[] = [];
|
||||
var functionName = signature.getScopedNameEx(enclosingScopeSymbol).toString();
|
||||
if (!functionName && (!symbol.isType() || (<TypeScript.PullTypeSymbol>symbol).isNamedTypeSymbol())) {
|
||||
functionName = symbol.getScopedNameEx(enclosingScopeSymbol).toString();
|
||||
}
|
||||
|
||||
var signatureMemberName = signature.getSignatureTypeNameEx(functionName, /*shortform*/ false, /*brackets*/ false, enclosingScopeSymbol, /*getParamMarkerInfo*/ true, /*getTypeParameterMarkerInfo*/ true);
|
||||
signatureGroupInfo.signatureInfo = TypeScript.MemberName.memberNameToString(signatureMemberName, paramIndexInfo);
|
||||
signatureGroupInfo.docComment = signature.docComments();
|
||||
|
||||
var parameterMarkerIndex = 0;
|
||||
|
||||
if (signature.isGeneric()) {
|
||||
var typeParameters = signature.getTypeParameters();
|
||||
for (var j = 0, m = typeParameters.length; j < m; j++) {
|
||||
var typeParameter = typeParameters[j];
|
||||
var signatureTypeParameterInfo = new FormalTypeParameterInfo();
|
||||
signatureTypeParameterInfo.name = typeParameter.getDisplayName();
|
||||
signatureTypeParameterInfo.docComment = typeParameter.docComments();
|
||||
signatureTypeParameterInfo.minChar = paramIndexInfo[2 * parameterMarkerIndex];
|
||||
signatureTypeParameterInfo.limChar = paramIndexInfo[2 * parameterMarkerIndex + 1];
|
||||
parameterMarkerIndex++;
|
||||
signatureGroupInfo.typeParameters.push(signatureTypeParameterInfo);
|
||||
}
|
||||
}
|
||||
|
||||
var parameters = signature.parameters;
|
||||
for (var j = 0, m = parameters.length; j < m; j++) {
|
||||
var parameter = parameters[j];
|
||||
var signatureParameterInfo = new FormalParameterInfo();
|
||||
signatureParameterInfo.isVariable = signature.hasVarArgs && (j === parameters.length - 1);
|
||||
signatureParameterInfo.name = parameter.getDisplayName();
|
||||
signatureParameterInfo.docComment = parameter.docComments();
|
||||
signatureParameterInfo.minChar = paramIndexInfo[2 * parameterMarkerIndex];
|
||||
signatureParameterInfo.limChar = paramIndexInfo[2 * parameterMarkerIndex + 1];
|
||||
parameterMarkerIndex++;
|
||||
signatureGroupInfo.parameters.push(signatureParameterInfo);
|
||||
}
|
||||
|
||||
signatureGroup.push(signatureGroupInfo);
|
||||
}
|
||||
|
||||
return signatureGroup;
|
||||
}
|
||||
|
||||
public static getSignatureInfoFromGenericSymbol(symbol: TypeScript.PullSymbol, enclosingScopeSymbol: TypeScript.PullSymbol, compilerState: LanguageServiceCompiler) {
|
||||
var signatureGroupInfo = new FormalSignatureItemInfo();
|
||||
|
||||
var paramIndexInfo: number[] = [];
|
||||
var symbolName = symbol.getScopedNameEx(enclosingScopeSymbol, /*skipTypeParametersInName*/ false, /*useConstaintInName*/ true, /*getPrettyTypeName*/ false, /*getTypeParamMarkerInfo*/ true);
|
||||
|
||||
signatureGroupInfo.signatureInfo = TypeScript.MemberName.memberNameToString(symbolName, paramIndexInfo);
|
||||
signatureGroupInfo.docComment = symbol.docComments();
|
||||
|
||||
var typeSymbol = symbol.type;
|
||||
|
||||
var typeParameters = typeSymbol.getTypeParameters();
|
||||
for (var i = 0, n = typeParameters.length; i < n; i++) {
|
||||
var typeParameter = typeParameters[i];
|
||||
var signatureTypeParameterInfo = new FormalTypeParameterInfo();
|
||||
signatureTypeParameterInfo.name = typeParameter.getDisplayName();
|
||||
signatureTypeParameterInfo.docComment = typeParameter.docComments();
|
||||
signatureTypeParameterInfo.minChar = paramIndexInfo[2 * i];
|
||||
signatureTypeParameterInfo.limChar = paramIndexInfo[2 * i + 1];
|
||||
signatureGroupInfo.typeParameters.push(signatureTypeParameterInfo);
|
||||
}
|
||||
|
||||
return [signatureGroupInfo];
|
||||
}
|
||||
|
||||
public static getActualSignatureInfoFromCallExpression(ast: IExpressionWithArgumentListSyntax, caretPosition: number, typeParameterInformation: IPartiallyWrittenTypeArgumentListInformation): ActualSignatureInfo {
|
||||
if (!ast) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var result = new ActualSignatureInfo();
|
||||
|
||||
// The expression is not guaranteed to be complete, we need to populate the min and lim with the most accurate information we have about
|
||||
// type argument and argument lists
|
||||
var parameterMinChar = caretPosition;
|
||||
var parameterLimChar = caretPosition;
|
||||
|
||||
if (ast.argumentList.typeArgumentList) {
|
||||
parameterMinChar = Math.min(start(ast.argumentList.typeArgumentList));
|
||||
parameterLimChar = Math.max(Math.max(start(ast.argumentList.typeArgumentList), end(ast.argumentList.typeArgumentList) + trailingTriviaWidth(ast.argumentList.typeArgumentList)));
|
||||
}
|
||||
|
||||
if (ast.argumentList.arguments) {
|
||||
parameterMinChar = Math.min(parameterMinChar, end(ast.argumentList.openParenToken));
|
||||
parameterLimChar = Math.max(parameterLimChar,
|
||||
ast.argumentList.closeParenToken.fullWidth() > 0 ? start(ast.argumentList.closeParenToken) : fullEnd(ast.argumentList));
|
||||
}
|
||||
|
||||
result.parameterMinChar = parameterMinChar;
|
||||
result.parameterLimChar = parameterLimChar;
|
||||
result.currentParameterIsTypeParameter = false;
|
||||
result.currentParameter = -1;
|
||||
|
||||
if (typeParameterInformation) {
|
||||
result.currentParameterIsTypeParameter = true;
|
||||
result.currentParameter = typeParameterInformation.argumentIndex;
|
||||
}
|
||||
else if (ast.argumentList.arguments && ast.argumentList.arguments.length > 0) {
|
||||
result.currentParameter = 0;
|
||||
for (var index = 0; index < ast.argumentList.arguments.length; index++) {
|
||||
if (caretPosition > end(ast.argumentList.arguments[index]) + lastToken(ast.argumentList.arguments[index]).trailingTriviaWidth()) {
|
||||
result.currentParameter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static getActualSignatureInfoFromPartiallyWritenGenericExpression(caretPosition: number, typeParameterInformation: IPartiallyWrittenTypeArgumentListInformation): ActualSignatureInfo {
|
||||
var result = new ActualSignatureInfo();
|
||||
|
||||
result.parameterMinChar = start(typeParameterInformation.lessThanToken);
|
||||
result.parameterLimChar = Math.max(fullEnd(typeParameterInformation.lessThanToken), caretPosition);
|
||||
result.currentParameterIsTypeParameter = true;
|
||||
result.currentParameter = typeParameterInformation.argumentIndex;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static isSignatureHelpBlocker(sourceUnit: TypeScript.SourceUnitSyntax, position: number): boolean {
|
||||
// We shouldn't be getting a possition that is outside the file because
|
||||
// isEntirelyInsideComment can't handle when the position is out of bounds,
|
||||
// callers should be fixed, however we should be resiliant to bad inputs
|
||||
// so we return true (this position is a blocker for getting signature help)
|
||||
if (position < 0 || position > fullWidth(sourceUnit)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return TypeScript.Syntax.isEntirelyInsideComment(sourceUnit, position);
|
||||
}
|
||||
|
||||
public static isTargetOfObjectCreationExpression(positionedToken: TypeScript.ISyntaxToken): boolean {
|
||||
var positionedParent = TypeScript.Syntax.getAncestorOfKind(positionedToken, TypeScript.SyntaxKind.ObjectCreationExpression);
|
||||
if (positionedParent) {
|
||||
var objectCreationExpression = <TypeScript.ObjectCreationExpressionSyntax> positionedParent;
|
||||
var expressionRelativeStart = objectCreationExpression.newKeyword.fullWidth();
|
||||
var tokenRelativeStart = positionedToken.fullStart() - fullStart(positionedParent);
|
||||
return tokenRelativeStart >= expressionRelativeStart &&
|
||||
tokenRelativeStart <= (expressionRelativeStart + fullWidth(objectCreationExpression.expression));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static moveBackUpTillMatchingTokenKind(token: TypeScript.ISyntaxToken, tokenKind: TypeScript.SyntaxKind, matchingTokenKind: TypeScript.SyntaxKind): TypeScript.ISyntaxToken {
|
||||
if (!token || token.kind() !== tokenKind) {
|
||||
throw TypeScript.Errors.invalidOperation();
|
||||
}
|
||||
|
||||
// Skip the current token
|
||||
token = previousToken(token, /*includeSkippedTokens*/ true);
|
||||
|
||||
var stack = 0;
|
||||
|
||||
while (token) {
|
||||
if (token.kind() === matchingTokenKind) {
|
||||
if (stack === 0) {
|
||||
// Found the matching token, return
|
||||
return token;
|
||||
}
|
||||
else if (stack < 0) {
|
||||
// tokens overlapped.. bail out.
|
||||
break;
|
||||
}
|
||||
else {
|
||||
stack--;
|
||||
}
|
||||
}
|
||||
else if (token.kind() === tokenKind) {
|
||||
stack++;
|
||||
}
|
||||
|
||||
// Move back
|
||||
token = previousToken(token, /*includeSkippedTokens*/ true);
|
||||
}
|
||||
|
||||
// Did not find matching token
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
// These utilities are common to multiple language service features.
|
||||
module ts {
|
||||
export interface ListItemInfo {
|
||||
listItemIndex: number;
|
||||
list: Node;
|
||||
}
|
||||
|
||||
export function findListItemInfo(node: Node): ListItemInfo {
|
||||
var syntaxList = findContainingList(node);
|
||||
var children = syntaxList.getChildren();
|
||||
var index = indexOf(children, node);
|
||||
|
||||
return {
|
||||
listItemIndex: index,
|
||||
list: syntaxList
|
||||
};
|
||||
}
|
||||
|
||||
export function findContainingList(node: Node): Node {
|
||||
// The node might be a list element (nonsynthetic) or a comma (synthetic). Either way, it will
|
||||
// be parented by the container of the SyntaxList, not the SyntaxList itself.
|
||||
// In order to find the list item index, we first need to locate SyntaxList itself and then search
|
||||
// for the position of the relevant node (or comma).
|
||||
var syntaxList = forEach(node.parent.getChildren(), c => {
|
||||
// find syntax list that covers the span of the node
|
||||
if (c.kind == SyntaxKind.SyntaxList && c.pos <= node.pos && c.end >= node.end) {
|
||||
return c;
|
||||
}
|
||||
});
|
||||
|
||||
return syntaxList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Includes the start position of each child, but excludes the end.
|
||||
*/
|
||||
export function findListItemIndexContainingPosition(list: Node, position: number): number {
|
||||
Debug.assert(list.kind === SyntaxKind.SyntaxList);
|
||||
var children = list.getChildren();
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
if (children[i].pos <= position && children[i].end > position) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/** Get a token that contains the position. This is guaranteed to return a token, the position can be in the
|
||||
* leading trivia or within the token text.
|
||||
*/
|
||||
export function getTokenAtPosition(sourceFile: SourceFile, position: number) {
|
||||
var current: Node = sourceFile;
|
||||
outer: while (true) {
|
||||
// find the child that has this
|
||||
for (var i = 0, n = current.getChildCount(); i < n; i++) {
|
||||
var child = current.getChildAt(i);
|
||||
if (child.getFullStart() <= position && position < child.getEnd()) {
|
||||
current = child;
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
return current;
|
||||
}
|
||||
}
|
||||
|
||||
/** Get the token whose text contains the position, or the containing node. */
|
||||
export function getNodeAtPosition(sourceFile: SourceFile, position: number) {
|
||||
var current: Node = sourceFile;
|
||||
outer: while (true) {
|
||||
// find the child that has this
|
||||
for (var i = 0, n = current.getChildCount(); i < n; i++) {
|
||||
var child = current.getChildAt(i);
|
||||
if (child.getStart() <= position && position < child.getEnd()) {
|
||||
current = child;
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
return current;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The token on the left of the position is the token that strictly includes the position
|
||||
* or sits to the left of the cursor if it is on a boundary. For example
|
||||
*
|
||||
* fo|o -> will return foo
|
||||
* foo <comment> |bar -> will return foo
|
||||
*
|
||||
*/
|
||||
export function findTokenOnLeftOfPosition(file: SourceFile, position: number): Node {
|
||||
// Ideally, getTokenAtPosition should return a token. However, it is currently
|
||||
// broken, so we do a check to make sure the result was indeed a token.
|
||||
var tokenAtPosition = getTokenAtPosition(file, position);
|
||||
if (isToken(tokenAtPosition) && position > tokenAtPosition.getStart(file) && position < tokenAtPosition.getEnd()) {
|
||||
return tokenAtPosition;
|
||||
}
|
||||
|
||||
return findPrecedingToken(position, file);
|
||||
}
|
||||
|
||||
export function findNextToken(previousToken: Node, parent: Node): Node {
|
||||
return find(parent);
|
||||
|
||||
function find(n: Node): Node {
|
||||
if (isToken(n) && n.pos === previousToken.end) {
|
||||
// this is token that starts at the end of previous token - return it
|
||||
return n;
|
||||
}
|
||||
|
||||
var children = n.getChildren();
|
||||
for (var i = 0, len = children.length; i < len; ++i) {
|
||||
var child = children[i];
|
||||
var shouldDiveInChildNode =
|
||||
// previous token is enclosed somewhere in the child
|
||||
(child.pos <= previousToken.pos && child.end > previousToken.end) ||
|
||||
// previous token ends exactly at the beginning of child
|
||||
(child.pos === previousToken.end);
|
||||
|
||||
if (shouldDiveInChildNode && nodeHasTokens(child)) {
|
||||
return find(child);
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function findPrecedingToken(position: number, sourceFile: SourceFile): Node {
|
||||
return find(sourceFile);
|
||||
|
||||
function findRightmostToken(n: Node): Node {
|
||||
if (isToken(n)) {
|
||||
return n;
|
||||
}
|
||||
|
||||
var children = n.getChildren();
|
||||
var candidate = findRightmostChildNodeWithTokens(children, /*exclusiveStartPosition*/ children.length);
|
||||
return candidate && findRightmostToken(candidate);
|
||||
|
||||
}
|
||||
|
||||
function find(n: Node): Node {
|
||||
if (isToken(n)) {
|
||||
return n;
|
||||
}
|
||||
|
||||
var children = n.getChildren();
|
||||
for (var i = 0, len = children.length; i < len; ++i) {
|
||||
var child = children[i];
|
||||
if (nodeHasTokens(child)) {
|
||||
if (position < child.end) {
|
||||
if (child.getStart(sourceFile) >= position) {
|
||||
// actual start of the node is past the position - previous token should be at the end of previous child
|
||||
var candidate = findRightmostChildNodeWithTokens(children, /*exclusiveStartPosition*/ i);
|
||||
return candidate && findRightmostToken(candidate)
|
||||
}
|
||||
else {
|
||||
// candidate should be in this node
|
||||
return find(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Debug.assert(n.kind === SyntaxKind.SourceFile);
|
||||
|
||||
// Here we know that none of child token nodes embrace the position,
|
||||
// the only known case is when position is at the end of the file.
|
||||
// Try to find the rightmost token in the file without filtering.
|
||||
// Namely we are skipping the check: 'position < node.end'
|
||||
if (children.length) {
|
||||
var candidate = findRightmostChildNodeWithTokens(children, /*exclusiveStartPosition*/ children.length);
|
||||
return candidate && findRightmostToken(candidate);
|
||||
}
|
||||
}
|
||||
|
||||
/// finds last node that is considered as candidate for search (isCandidate(node) === true) starting from 'exclusiveStartPosition'
|
||||
function findRightmostChildNodeWithTokens(children: Node[], exclusiveStartPosition: number): Node {
|
||||
for (var i = exclusiveStartPosition - 1; i >= 0; --i) {
|
||||
if (nodeHasTokens(children[i])) {
|
||||
return children[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function nodeHasTokens(n: Node): boolean {
|
||||
if (n.kind === SyntaxKind.ExpressionStatement) {
|
||||
return nodeHasTokens((<ExpressionStatement>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 isToken(n: Node): boolean {
|
||||
return n.kind >= SyntaxKind.FirstToken && n.kind <= SyntaxKind.LastToken;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user