This commit is contained in:
Yui T
2014-10-01 14:27:10 -07:00
63 changed files with 2220 additions and 479 deletions
+47 -19
View File
@@ -92,7 +92,9 @@ module ts {
getContextualType: getContextualType,
getFullyQualifiedName: getFullyQualifiedName,
getResolvedSignature: getResolvedSignature,
getEnumMemberValue: getEnumMemberValue
getEnumMemberValue: getEnumMemberValue,
isValidPropertyAccess: isValidPropertyAccess,
getAliasedSymbol: resolveImport
};
var undefinedSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "undefined");
@@ -4239,6 +4241,25 @@ module ts {
return anyType;
}
function isValidPropertyAccess(node: PropertyAccess, propertyName: string): boolean {
var type = checkExpression(node.left);
if (type !== unknownType && type !== anyType) {
var apparentType = getApparentType(getWidenedType(type));
var prop = getPropertyOfApparentType(apparentType, propertyName);
if (prop && prop.parent && prop.parent.flags & SymbolFlags.Class) {
if (node.left.kind === SyntaxKind.SuperKeyword && getDeclarationKindFromSymbol(prop) !== SyntaxKind.Method) {
return false;
}
else {
var diagnosticsCount = diagnostics.length;
checkClassPropertyAccess(node, type, prop);
return diagnostics.length === diagnosticsCount
}
}
}
return true;
}
function checkIndexedAccess(node: IndexedAccess): Type {
var objectType = checkExpression(node.object);
var indexType = checkExpression(node.index);
@@ -4311,25 +4332,32 @@ module ts {
}
function signatureHasCorrectArity(node: CallExpression, signature: Signature): boolean {
var args = node.arguments || emptyArray;
var isCorrect = args.length >= signature.minArgumentCount &&
(signature.hasRestParameter || args.length <= signature.parameters.length) &&
(!node.typeArguments || signature.typeParameters && node.typeArguments.length === signature.typeParameters.length);
// 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;
}
if (!node.arguments) {
// This only happens when we have something of the form:
// new C
//
return signature.minArgumentCount === 0;
}
return isCorrect;
// For IDE scenarios, since we may have an incomplete call, we make two modifications
// to arity checking.
// 1. A trailing comma is tantamount to adding another argument
// 2. If the call is incomplete (no closing paren) allow fewer arguments than expected
var args = node.arguments;
var numberOfArgs = args.hasTrailingComma ? args.length + 1 : args.length;
var hasTooManyArguments = !signature.hasRestParameter && numberOfArgs > signature.parameters.length;
var hasRightNumberOfTypeArguments = !node.typeArguments ||
(signature.typeParameters && node.typeArguments.length === signature.typeParameters.length);
if (hasTooManyArguments || !hasRightNumberOfTypeArguments) {
return false;
}
// If we are missing the close paren, the call is incomplete, and we should skip
// the lower bound check.
var callIsIncomplete = args.end === node.end;
var hasEnoughArguments = numberOfArgs >= signature.minArgumentCount;
return callIsIncomplete || hasEnoughArguments;
}
// If type has a single call signature and no other members, return that signature. Otherwise, return undefined.
@@ -5,6 +5,7 @@ module ts {
Unterminated_string_literal: { code: 1002, category: DiagnosticCategory.Error, key: "Unterminated string literal." },
Identifier_expected: { code: 1003, category: DiagnosticCategory.Error, key: "Identifier expected." },
_0_expected: { code: 1005, category: DiagnosticCategory.Error, key: "'{0}' expected." },
A_file_cannot_have_a_reference_to_itself: { code: 1006, category: DiagnosticCategory.Error, key: "A file cannot have a reference to itself." },
Trailing_comma_not_allowed: { code: 1009, category: DiagnosticCategory.Error, key: "Trailing comma not allowed." },
Asterisk_Slash_expected: { code: 1010, category: DiagnosticCategory.Error, key: "'*/' expected." },
Unexpected_token: { code: 1012, category: DiagnosticCategory.Error, key: "Unexpected token." },
+5
View File
@@ -10,6 +10,10 @@
"'{0}' expected.": {
"category": "Error",
"code": 1005
},
"A file cannot have a reference to itself.": {
"category": "Error",
"code": 1006
},
"Trailing comma not allowed.": {
"category": "Error",
@@ -1355,6 +1359,7 @@
"category": "Error",
"code": 5001
},
"Cannot find the common subdirectory path for the input files.": {
"category": "Error",
"code": 5009
+27 -17
View File
@@ -1073,7 +1073,7 @@ module ts {
case ParsingContext.TypeParameters:
return isIdentifier();
case ParsingContext.ArgumentExpressions:
return isExpression();
return token === SyntaxKind.CommaToken || isExpression();
case ParsingContext.ArrayLiteralMembers:
return token === SyntaxKind.CommaToken || isExpression();
case ParsingContext.Parameters:
@@ -1226,18 +1226,6 @@ module ts {
error(Diagnostics._0_expected, ",");
}
else if (isListTerminator(kind)) {
// Check if the last token was a comma.
if (commaStart >= 0) {
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);
}
}
// Always preserve a trailing comma by marking it on the NodeArray
result.hasTrailingComma = true;
}
break;
}
else {
@@ -1248,6 +1236,23 @@ module ts {
nextToken();
}
}
// Recording the trailing comma is deliberately done after the previous
// loop, and not just if we see a list terminator. This is because the list
// may have ended incorrectly, but it is still important to know if there
// was a trailing comma.
// Check if the last token was a comma.
if (commaStart >= 0) {
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);
}
}
// Always preserve a trailing comma by marking it on the NodeArray
result.hasTrailingComma = true;
}
result.end = getNodeEnd();
parsingContext = saveParsingContext;
return result;
@@ -3826,15 +3831,17 @@ module ts {
}
else {
var matchResult = fullTripleSlashReferencePathRegEx.exec(comment);
var start = range.pos;
var end = range.end;
var length = end - start;
if (!matchResult) {
var start = range.pos;
var length = range.end - start;
errorAtPos(start, length, Diagnostics.Invalid_reference_directive_syntax);
}
else {
referencedFiles.push({
pos: range.pos,
end: range.end,
pos: start,
end: end,
filename: matchResult[3]
});
}
@@ -3951,6 +3958,9 @@ module ts {
else if (!findSourceFile(filename, isDefaultLib, refFile, refPos, refEnd)) {
diagnostic = Diagnostics.File_0_not_found;
}
else if (refFile && host.getCanonicalFileName(filename) === host.getCanonicalFileName(refFile.filename)) {
diagnostic = Diagnostics.A_file_cannot_have_a_reference_to_itself;
}
}
else {
if (!(findSourceFile(filename + ".ts", isDefaultLib, refFile, refPos, refEnd) || findSourceFile(filename + ".d.ts", isDefaultLib, refFile, refPos, refEnd))) {
+3
View File
@@ -655,6 +655,9 @@ module ts {
// Returns the constant value of this enum member, or 'undefined' if the enum member has a
// computed value.
getEnumMemberValue(node: EnumMember): number;
isValidPropertyAccess(node: PropertyAccess, propertyName: string): boolean;
getAliasedSymbol(symbol: Symbol): Symbol;
}
export interface TextWriter {
+74 -75
View File
@@ -412,6 +412,11 @@ module FourSlash {
}
}
private raiseError(message: string) {
message = "Marker: " + currentTestState.lastKnownMarker + "\n" + message;
throw new Error(message);
}
private getDiagnostics(fileName: string): ts.Diagnostic[] {
var syntacticErrors = this.languageService.getSyntacticDiagnostics(fileName);
var semanticErrors = this.languageService.getSemanticDiagnostics(fileName);
@@ -500,7 +505,7 @@ module FourSlash {
this.printErrorLog(false, errors);
var errorMsg = "Actual number of errors (" + actual + ") does not match expected number (" + expected + ")";
Harness.IO.log(errorMsg);
throw new Error(errorMsg);
this.raiseError(errorMsg);
}
}
@@ -514,7 +519,7 @@ module FourSlash {
var evaluation = new Function(emit.outputFiles[0].text + ';\r\nreturn (' + expr + ');')();
if (evaluation !== value) {
throw new Error('Expected evaluation of expression "' + expr + '" to equal "' + value + '", but got "' + evaluation + '"');
this.raiseError('Expected evaluation of expression "' + expr + '" to equal "' + value + '", but got "' + evaluation + '"');
}
}
@@ -531,7 +536,7 @@ module FourSlash {
this.assertItemInCompletionList(members.entries, symbol, type, docComment, fullSymbolName, kind);
}
else {
throw new Error("Expected a member list, but none was provided");
this.raiseError("Expected a member list, but none was provided");
}
}
@@ -554,11 +559,11 @@ module FourSlash {
var match = members.entries.length === expectedCount;
if ((!match && !negative) || (match && negative)) {
throw new Error("Member list count was " + members.entries.length + ". Expected " + expectedCount);
this.raiseError("Member list count was " + members.entries.length + ". Expected " + expectedCount);
}
}
else if (expectedCount) {
throw new Error("Member list count was 0. Expected " + expectedCount);
this.raiseError("Member list count was 0. Expected " + expectedCount);
}
}
@@ -568,7 +573,7 @@ module FourSlash {
var members = this.getMemberListAtCaret();
if (members.entries.filter(e => e.name === symbol).length !== 0) {
throw new Error('Member list did contain ' + symbol);
this.raiseError('Member list did contain ' + symbol);
}
}
@@ -579,7 +584,7 @@ module FourSlash {
var itemsCount = completions.entries.length;
if (itemsCount <= count) {
throw new Error('Expected completion list items count to be greater than ' + count + ', but is actually ' + itemsCount);
this.raiseError('Expected completion list items count to be greater than ' + count + ', but is actually ' + itemsCount);
}
}
@@ -592,7 +597,7 @@ module FourSlash {
var members = this.getMemberListAtCaret();
if ((!members || members.entries.length === 0) && negative) {
throw new Error("Member list is empty at Caret");
this.raiseError("Member list is empty at Caret");
} else if ((members && members.entries.length !== 0) && !negative) {
var errorMsg = "\n" + "Member List contains: [" + members.entries[0].name;
@@ -602,7 +607,7 @@ module FourSlash {
errorMsg += "]\n";
Harness.IO.log(errorMsg);
throw new Error("Member list is not empty at Caret");
this.raiseError("Member list is not empty at Caret");
}
}
@@ -612,7 +617,7 @@ module FourSlash {
var completions = this.getCompletionListAtCaret();
if ((!completions || completions.entries.length === 0) && negative) {
throw new Error("Completion list is empty at Caret");
this.raiseError("Completion list is empty at Caret");
} else if ((completions && completions.entries.length !== 0) && !negative) {
var errorMsg = "\n" + "Completion List contains: [" + completions.entries[0].name;
@@ -622,7 +627,7 @@ module FourSlash {
errorMsg += "]\n";
Harness.IO.log(errorMsg);
throw new Error("Completion list is not empty at Caret");
this.raiseError("Completion list is not empty at Caret");
}
}
@@ -638,7 +643,7 @@ module FourSlash {
var completions = this.getCompletionListAtCaret();
if (completions && completions.entries && completions.entries.filter(e => e.name === symbol).length !== 0) {
throw new Error('Completion list did contain ' + symbol);
this.raiseError('Completion list did contain ' + symbol);
}
}
@@ -668,21 +673,21 @@ module FourSlash {
var references = this.getReferencesAtCaret();
if (!references || references.length === 0) {
throw new Error('verifyReferencesAtPositionListContains failed - found 0 references, expected at least one.');
this.raiseError('verifyReferencesAtPositionListContains failed - found 0 references, expected at least one.');
}
for (var i = 0; i < references.length; i++) {
var reference = references[i];
if (reference && reference.fileName === fileName && reference.textSpan.start() === start && reference.textSpan.end() === end) {
if (typeof isWriteAccess !== "undefined" && reference.isWriteAccess !== isWriteAccess) {
throw new Error('verifyReferencesAtPositionListContains failed - item isWriteAccess value doe not match, actual: ' + reference.isWriteAccess + ', expected: ' + isWriteAccess + '.');
this.raiseError('verifyReferencesAtPositionListContains failed - item isWriteAccess value doe not match, actual: ' + reference.isWriteAccess + ', expected: ' + isWriteAccess + '.');
}
return;
}
}
var missingItem = { fileName: fileName, start: start, end: end, isWriteAccess: isWriteAccess };
throw new Error('verifyReferencesAtPositionListContains failed - could not find the item: ' + JSON.stringify(missingItem) + ' in the returned list: (' + JSON.stringify(references) + ')');
this.raiseError('verifyReferencesAtPositionListContains failed - could not find the item: ' + JSON.stringify(missingItem) + ' in the returned list: (' + JSON.stringify(references) + ')');
}
public verifyReferencesCountIs(count: number, localFilesOnly: boolean = true) {
@@ -706,7 +711,7 @@ module FourSlash {
if (referencesCount !== count) {
var condition = localFilesOnly ? "excluding libs" : "including libs";
throw new Error("Expected references count (" + condition + ") to be " + count + ", but is actually " + referencesCount);
this.raiseError("Expected references count (" + condition + ") to be " + count + ", but is actually " + referencesCount);
}
}
@@ -729,7 +734,7 @@ module FourSlash {
if (implementorsCount !== count) {
var condition = localFilesOnly ? "excluding libs" : "including libs";
throw new Error("Expected implementors count (" + condition + ") to be " + count + ", but is actually " + implementors.length);
this.raiseError("Expected implementors count (" + condition + ") to be " + count + ", but is actually " + implementors.length);
}
}
@@ -806,12 +811,12 @@ module FourSlash {
var actualQuickInfo = this.languageService.getTypeAtPosition(this.activeFile.fileName, this.currentCaretPosition);
if (negative) {
if (actualQuickInfo) {
throw new Error('verifyQuickInfoExists failed. Expected quick info NOT to exist');
this.raiseError('verifyQuickInfoExists failed. Expected quick info NOT to exist');
}
}
else {
if (!actualQuickInfo) {
throw new Error('verifyQuickInfoExists failed. Expected quick info to exist');
this.raiseError('verifyQuickInfoExists failed. Expected quick info to exist');
}
}
}
@@ -886,31 +891,38 @@ module FourSlash {
assert.equal(actual, expected);
}
public verifySignatureHelpArgumentCount(expected: number) {
this.taoInvalidReason = 'verifySignatureHelpArgumentCount NYI';
var signatureHelpItems = this.languageService.getSignatureHelpItems(this.activeFile.fileName, this.currentCaretPosition);
var actual = signatureHelpItems.argumentCount;
assert.equal(actual, expected);
}
public verifySignatureHelpPresent(shouldBePresent = true) {
this.taoInvalidReason = 'verifySignatureHelpPresent NYI';
var actual = this.languageService.getSignatureHelpItems(this.activeFile.fileName, this.currentCaretPosition);
if (shouldBePresent) {
if (!actual) {
throw new Error("Expected signature help to be present, but it wasn't");
this.raiseError("Expected signature help to be present, but it wasn't");
}
} else {
if (actual) {
throw new Error("Expected no signature help, but got '" + JSON.stringify(actual) + "'");
this.raiseError("Expected no signature help, but got '" + JSON.stringify(actual) + "'");
}
}
}
private validate(name: string, expected: string, actual: string) {
if (expected && expected !== actual) {
throw new Error("Expected " + name + " '" + expected + "'. Got '" + actual + "' instead.");
this.raiseError("Expected " + name + " '" + expected + "'. Got '" + actual + "' instead.");
}
}
public verifyRenameInfoSucceeded(displayName?: string, fullDisplayName?: string, kind?: string, kindModifiers?: string) {
var renameInfo = this.languageService.getRenameInfo(this.activeFile.fileName, this.currentCaretPosition);
if (!renameInfo.canRename) {
throw new Error("Rename did not succeed");
this.raiseError("Rename did not succeed");
}
this.validate("displayName", displayName, renameInfo.displayName);
@@ -919,13 +931,13 @@ module FourSlash {
this.validate("kindModifiers", kindModifiers, renameInfo.kindModifiers);
if (this.getRanges().length !== 1) {
throw new Error("Expected a single range to be selected in the test file.");
this.raiseError("Expected a single range to be selected in the test file.");
}
var expectedRange = this.getRanges()[0];
if (renameInfo.triggerSpan.start() !== expectedRange.start ||
renameInfo.triggerSpan.end() !== expectedRange.end) {
throw new Error("Expected triggerSpan [" + expectedRange.start + "," + expectedRange.end + "). Got [" +
this.raiseError("Expected triggerSpan [" + expectedRange.start + "," + expectedRange.end + "). Got [" +
renameInfo.triggerSpan.start() + "," + renameInfo.triggerSpan.end() + ") instead.");
}
}
@@ -933,35 +945,22 @@ module FourSlash {
public verifyRenameInfoFailed(message?: string) {
var renameInfo = this.languageService.getRenameInfo(this.activeFile.fileName, this.currentCaretPosition);
if (renameInfo.canRename) {
throw new Error("Rename was expected to fail");
this.raiseError("Rename was expected to fail");
}
this.validate("error", message, renameInfo.localizedErrorMessage);
}
//private getFormalParameter() {
// var help = this.languageService.getSignatureHelpItems(this.activeFile.fileName, this.currentCaretPosition);
// return help.formal;
//}
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),
// 'activeFormal' will be -1 (even if there is only 1 signature). Signature help will show the
// first signature in the signature group, so go with that
var index = help.selectedItemIndex < 0 ? 0 : help.selectedItemIndex;
var index = help.selectedItemIndex;
return help.items[index];
}
private getActiveParameter(): ts.SignatureHelpParameter {
var help = this.languageService.getSignatureHelpItems(this.activeFile.fileName, this.currentCaretPosition);
var item = help.items[help.selectedItemIndex];
// Same logic as in getActiveSignatureHelp - this value might be -1 until a parameter value actually gets typed
var currentParam = help.argumentIndex < 0 ? 0 : help.argumentIndex;
var currentParam = help.argumentIndex;
return item.parameters[currentParam];
}
@@ -1009,7 +1008,7 @@ module FourSlash {
// If there is not emiThisFile flag specified in the test file, throw an error
if (emitFiles.length === 0) {
throw new Error("No emitThisFile is specified in the test file");
this.raiseError("No emitThisFile is specified in the test file");
}
Harness.Baseline.runBaseline(
@@ -1309,7 +1308,7 @@ module FourSlash {
//var fullSyntaxErrs = JSON.stringify(refSyntaxTree.diagnostics());
//if (incrSyntaxErrs !== fullSyntaxErrs) {
// throw new Error('Mismatched incremental/full syntactic errors for file ' + this.activeFile.fileName + '.\n=== Incremental errors ===\n' + incrSyntaxErrs + '\n=== Full Errors ===\n' + fullSyntaxErrs);
// this.raiseError('Mismatched incremental/full syntactic errors for file ' + this.activeFile.fileName + '.\n=== Incremental errors ===\n' + incrSyntaxErrs + '\n=== Full Errors ===\n' + fullSyntaxErrs);
//}
// if (this.editValidation !== IncrementalEditValidation.SyntacticOnly) {
@@ -1326,7 +1325,7 @@ module FourSlash {
// var incrSemanticErrs = JSON.stringify(this.languageService.getSemanticDiagnostics(this.testData.files[i].fileName));
// if (incrSemanticErrs !== refSemanticErrs) {
// throw new Error('Mismatched incremental/full semantic errors for file ' + this.testData.files[i].fileName + '\n=== Incremental errors ===\n' + incrSemanticErrs + '\n=== Full Errors ===\n' + refSemanticErrs);
// this.raiseError('Mismatched incremental/full semantic errors for file ' + this.testData.files[i].fileName + '\n=== Incremental errors ===\n' + incrSemanticErrs + '\n=== Full Errors ===\n' + refSemanticErrs);
// }
// }
// }
@@ -1365,7 +1364,7 @@ module FourSlash {
var newContent = snapshot.getText(0, snapshot.getLength());
if (newContent.replace(/\s/g, '') !== oldContent.replace(/\s/g, '')) {
throw new Error('Formatting operation destroyed non-whitespace content');
this.raiseError('Formatting operation destroyed non-whitespace content');
}
}
return runningOffset;
@@ -1422,11 +1421,11 @@ module FourSlash {
var definitions = this.languageService.getDefinitionAtPosition(this.activeFile.fileName, this.currentCaretPosition);
if (!definitions || !definitions.length) {
throw new Error('goToDefinition failed - expected to at least one definition location but got 0');
this.raiseError('goToDefinition failed - expected to at least one definition location but got 0');
}
if (definitionIndex >= definitions.length) {
throw new Error('goToDefinition failed - definitionIndex value (' + definitionIndex + ') exceeds definition list size (' + definitions.length + ')');
this.raiseError('goToDefinition failed - definitionIndex value (' + definitionIndex + ') exceeds definition list size (' + definitions.length + ')');
}
var definition = definitions[definitionIndex];
@@ -1442,10 +1441,10 @@ module FourSlash {
var foundDefinitions = definitions && definitions.length;
if (foundDefinitions && negative) {
throw new Error('goToDefinition - expected to 0 definition locations but got ' + definitions.length);
this.raiseError('goToDefinition - expected to 0 definition locations but got ' + definitions.length);
}
else if (!foundDefinitions && !negative) {
throw new Error('goToDefinition - expected to at least one definition location but got 0');
this.raiseError('goToDefinition - expected to at least one definition location but got 0');
}
}
@@ -1480,7 +1479,7 @@ module FourSlash {
var actual = this.getIndentation(this.activeFile.fileName, this.currentCaretPosition);
if (actual != numberOfSpaces) {
throw new Error('verifyIndentationAtCurrentPosition failed - expected: ' + numberOfSpaces + ', actual: ' + actual);
this.raiseError('verifyIndentationAtCurrentPosition failed - expected: ' + numberOfSpaces + ', actual: ' + actual);
}
}
@@ -1489,7 +1488,7 @@ module FourSlash {
var actual = this.getIndentation(fileName, position);
if (actual !== numberOfSpaces) {
throw new Error('verifyIndentationAtPosition failed - expected: ' + numberOfSpaces + ', actual: ' + actual);
this.raiseError('verifyIndentationAtPosition failed - expected: ' + numberOfSpaces + ', actual: ' + actual);
}
}
@@ -1532,14 +1531,14 @@ module FourSlash {
var span = this.languageService.getNameOrDottedNameSpan(this.activeFile.fileName, this.currentCaretPosition, this.currentCaretPosition);
if (span === null) {
throw new Error('verifyCurrentNameOrDottedNameSpanText\n' +
this.raiseError('verifyCurrentNameOrDottedNameSpanText\n' +
'\tExpected: "' + text + '"\n' +
'\t Actual: null');
}
var actual = this.languageServiceShimHost.getScriptSnapshot(this.activeFile.fileName).getText(span.start(), span.end());
if (actual !== text) {
throw new Error('verifyCurrentNameOrDottedNameSpanText\n' +
this.raiseError('verifyCurrentNameOrDottedNameSpanText\n' +
'\tExpected: "' + text + '"\n' +
'\t Actual: "' + actual + '"');
}
@@ -1577,7 +1576,7 @@ module FourSlash {
private verifyClassifications(expected: { classificationType: string; text: string }[], actual: ts.ClassifiedSpan[]) {
if (actual.length !== expected.length) {
throw new Error('verifySyntacticClassification failed - expected total classifications to be ' + expected.length + ', but was ' + actual.length);
this.raiseError('verifySyntacticClassification failed - expected total classifications to be ' + expected.length + ', but was ' + actual.length);
}
for (var i = 0; i < expected.length; i++) {
@@ -1586,7 +1585,7 @@ module FourSlash {
var expectedType: string = (<any>ts.ClassificationTypeNames)[expectedClassification.classificationType];
if (expectedType !== actualClassification.classificationType) {
throw new Error('verifySyntacticClassification failed - expected classifications type to be ' +
this.raiseError('verifySyntacticClassification failed - expected classifications type to be ' +
expectedType + ', but was ' +
actualClassification.classificationType);
}
@@ -1594,7 +1593,7 @@ module FourSlash {
var actualSpan = actualClassification.textSpan;
var actualText = this.activeFile.content.substr(actualSpan.start(), actualSpan.length());
if (expectedClassification.text !== actualText) {
throw new Error('verifySyntacticClassification failed - expected classificatied text to be ' +
this.raiseError('verifySyntacticClassification failed - expected classificatied text to be ' +
expectedClassification.text + ', but was ' +
actualText);
}
@@ -1621,14 +1620,14 @@ module FourSlash {
var actual = this.languageService.getOutliningSpans(this.activeFile.fileName);
if (actual.length !== spans.length) {
throw new Error('verifyOutliningSpans failed - expected total spans to be ' + spans.length + ', but was ' + actual.length);
this.raiseError('verifyOutliningSpans failed - expected total spans to be ' + spans.length + ', but was ' + actual.length);
}
for (var i = 0; i < spans.length; i++) {
var expectedSpan = spans[i];
var actualSpan = actual[i];
if (expectedSpan.start !== actualSpan.textSpan.start() || expectedSpan.end !== actualSpan.textSpan.end()) {
throw new Error('verifyOutliningSpans failed - span ' + (i + 1) + ' expected: (' + expectedSpan.start + ',' + expectedSpan.end + '), actual: (' + actualSpan.textSpan.start() + ',' + actualSpan.textSpan.end() + ')');
this.raiseError('verifyOutliningSpans failed - span ' + (i + 1) + ' expected: (' + expectedSpan.start + ',' + expectedSpan.end + '), actual: (' + actualSpan.textSpan.start() + ',' + actualSpan.textSpan.end() + ')');
}
}
}
@@ -1638,7 +1637,7 @@ module FourSlash {
descriptors.map(d => { return { text: d, priority: 0 }; }));
if (actual.length !== spans.length) {
throw new Error('verifyTodoComments failed - expected total spans to be ' + spans.length + ', but was ' + actual.length);
this.raiseError('verifyTodoComments failed - expected total spans to be ' + spans.length + ', but was ' + actual.length);
}
for (var i = 0; i < spans.length; i++) {
@@ -1647,7 +1646,7 @@ module FourSlash {
var actualCommentSpan = new TypeScript.TextSpan(actualComment.position, actualComment.message.length);
if (expectedSpan.start !== actualCommentSpan.start() || expectedSpan.end !== actualCommentSpan.end()) {
throw new Error('verifyOutliningSpans failed - span ' + (i + 1) + ' expected: (' + expectedSpan.start + ',' + expectedSpan.end + '), actual: (' + actualCommentSpan.start() + ',' + actualCommentSpan.end() + ')');
this.raiseError('verifyOutliningSpans failed - span ' + (i + 1) + ' expected: (' + expectedSpan.start + ',' + expectedSpan.end + '), actual: (' + actualCommentSpan.start() + ',' + actualCommentSpan.end() + ')');
}
}
}
@@ -1658,7 +1657,7 @@ module FourSlash {
var actual = this.languageService.getBraceMatchingAtPosition(this.activeFile.fileName, bracePosition);
if (actual.length !== 2) {
throw new Error('verifyMatchingBracePosition failed - expected result to contain 2 spans, but it had ' + actual.length);
this.raiseError('verifyMatchingBracePosition failed - expected result to contain 2 spans, but it had ' + actual.length);
}
var actualMatchPosition = -1;
@@ -1667,11 +1666,11 @@ module FourSlash {
} else if (bracePosition === actual[1].start()) {
actualMatchPosition = actual[0].start();
} else {
throw new Error('verifyMatchingBracePosition failed - could not find the brace position: ' + bracePosition + ' in the returned list: (' + actual[0].start() + ',' + actual[0].end() + ') and (' + actual[1].start() + ',' + actual[1].end() + ')');
this.raiseError('verifyMatchingBracePosition failed - could not find the brace position: ' + bracePosition + ' in the returned list: (' + actual[0].start() + ',' + actual[0].end() + ') and (' + actual[1].start() + ',' + actual[1].end() + ')');
}
if (actualMatchPosition !== expectedMatchPosition) {
throw new Error('verifyMatchingBracePosition failed - expected: ' + actualMatchPosition + ', actual: ' + expectedMatchPosition);
this.raiseError('verifyMatchingBracePosition failed - expected: ' + actualMatchPosition + ', actual: ' + expectedMatchPosition);
}
}
@@ -1681,7 +1680,7 @@ module FourSlash {
var actual = this.languageService.getBraceMatchingAtPosition(this.activeFile.fileName, bracePosition);
if (actual.length !== 0) {
throw new Error('verifyNoMatchingBracePosition failed - expected: 0 spans, actual: ' + actual.length);
this.raiseError('verifyNoMatchingBracePosition failed - expected: 0 spans, actual: ' + actual.length);
}
}
@@ -1768,7 +1767,7 @@ module FourSlash {
}
if (expected != actual) {
throw new Error('verifyNavigationItemsCount failed - found: ' + actual + ' navigation items, expected: ' + expected + '.');
this.raiseError('verifyNavigationItemsCount failed - found: ' + actual + ' navigation items, expected: ' + expected + '.');
}
}
@@ -1788,7 +1787,7 @@ module FourSlash {
var items = this.languageService.getNavigateToItems(searchValue);
if (!items || items.length === 0) {
throw new Error('verifyNavigationItemsListContains failed - found 0 navigation items, expected at least one.');
this.raiseError('verifyNavigationItemsListContains failed - found 0 navigation items, expected at least one.');
}
for (var i = 0; i < items.length; i++) {
@@ -1804,7 +1803,7 @@ module FourSlash {
// if there was an explicit match kind specified, then it should be validated.
if (matchKind !== undefined) {
var missingItem = { name: name, kind: kind, searchValue: searchValue, matchKind: matchKind, fileName: fileName, parentName: parentName };
throw new Error('verifyNavigationItemsListContains failed - could not find the item: ' + JSON.stringify(missingItem) + ' in the returned list: (' + JSON.stringify(items) + ')');
this.raiseError('verifyNavigationItemsListContains failed - could not find the item: ' + JSON.stringify(missingItem) + ' in the returned list: (' + JSON.stringify(items) + ')');
}
}
@@ -1815,7 +1814,7 @@ module FourSlash {
var actual = this.getNavigationBarItemsCount(items);
if (expected != actual) {
throw new Error('verifyGetScriptLexicalStructureListCount failed - found: ' + actual + ' navigation items, expected: ' + expected + '.');
this.raiseError('verifyGetScriptLexicalStructureListCount failed - found: ' + actual + ' navigation items, expected: ' + expected + '.');
}
}
@@ -1840,7 +1839,7 @@ module FourSlash {
var items = this.languageService.getNavigationBarItems(this.activeFile.fileName);
if (!items || items.length === 0) {
throw new Error('verifyGetScriptLexicalStructureListContains failed - found 0 navigation items, expected at least one.');
this.raiseError('verifyGetScriptLexicalStructureListContains failed - found 0 navigation items, expected at least one.');
}
if (this.navigationBarItemsContains(items, name, kind)) {
@@ -1848,7 +1847,7 @@ module FourSlash {
}
var missingItem = { name: name, kind: kind };
throw new Error('verifyGetScriptLexicalStructureListContains failed - could not find the item: ' + JSON.stringify(missingItem) + ' in the returned list: (' + JSON.stringify(items) + ')');
this.raiseError('verifyGetScriptLexicalStructureListContains failed - could not find the item: ' + JSON.stringify(missingItem) + ' in the returned list: (' + JSON.stringify(items) + ')');
}
private navigationBarItemsContains(items: ts.NavigationBarItem[], name: string, kind: string) {
@@ -1902,21 +1901,21 @@ module FourSlash {
var occurances = this.getOccurancesAtCurrentPosition();
if (!occurances || occurances.length === 0) {
throw new Error('verifyOccurancesAtPositionListContains failed - found 0 references, expected at least one.');
this.raiseError('verifyOccurancesAtPositionListContains failed - found 0 references, expected at least one.');
}
for (var i = 0; i < occurances.length; i++) {
var occurance = occurances[i];
if (occurance && occurance.fileName === fileName && occurance.textSpan.start() === start && occurance.textSpan.end() === end) {
if (typeof isWriteAccess !== "undefined" && occurance.isWriteAccess !== isWriteAccess) {
throw new Error('verifyOccurancesAtPositionListContains failed - item isWriteAccess value doe not match, actual: ' + occurance.isWriteAccess + ', expected: ' + isWriteAccess + '.');
this.raiseError('verifyOccurancesAtPositionListContains failed - item isWriteAccess value doe not match, actual: ' + occurance.isWriteAccess + ', expected: ' + isWriteAccess + '.');
}
return;
}
}
var missingItem = { fileName: fileName, start: start, end: end, isWriteAccess: isWriteAccess };
throw new Error('verifyOccurancesAtPositionListContains failed - could not find the item: ' + JSON.stringify(missingItem) + ' in the returned list: (' + JSON.stringify(occurances) + ')');
this.raiseError('verifyOccurancesAtPositionListContains failed - could not find the item: ' + JSON.stringify(missingItem) + ' in the returned list: (' + JSON.stringify(occurances) + ')');
}
public verifyOccurrencesAtPositionListCount(expectedCount: number) {
@@ -1925,7 +1924,7 @@ module FourSlash {
var occurances = this.getOccurancesAtCurrentPosition();
var actualCount = occurances ? occurances.length : 0;
if (expectedCount !== actualCount) {
throw new Error('verifyOccurrencesAtPositionListCount failed - actual: ' + actualCount + ', expected:' + expectedCount);
this.raiseError('verifyOccurrencesAtPositionListCount failed - actual: ' + actualCount + ', expected:' + expectedCount);
}
}
@@ -2010,7 +2009,7 @@ module FourSlash {
var itemsString = items.map((item) => JSON.stringify({ name: item.name, kind: item.kind })).join(",\n");
throw new Error("Marker: " + currentTestState.lastKnownMarker + "\n" + 'Expected "' + JSON.stringify({ name: name, type: type, docComment: docComment, fullSymbolName: fullSymbolName, kind: kind }) + '" to be in list [' + itemsString + ']');
this.raiseError('Expected "' + JSON.stringify({ name: name, type: type, docComment: docComment, fullSymbolName: fullSymbolName, kind: kind }) + '" to be in list [' + itemsString + ']');
}
private findFile(indexOrName: any) {
+204 -45
View File
@@ -256,7 +256,7 @@ module ts {
var declarations = this.getDeclarations();
if (declarations) {
for (var i = 0, n = declarations.length; i < n; i++) {
this.processDocumentationCommentDeclaration(lines, declarations[0]);
this.processDocumentationCommentDeclaration(lines, declarations[i]);
}
}
@@ -274,7 +274,7 @@ module ts {
for (var i = 0, n = commentRanges.length; i < n; i++) {
this.processDocumentationCommentRange(
lines, sourceFile, commentRanges[0]);
lines, sourceFile, commentRanges[i]);
}
}
}
@@ -1025,6 +1025,8 @@ module ts {
static primitiveType = "primitive type";
static label = "label";
static alias = "alias"
}
export class ScriptElementKindModifier {
@@ -2038,15 +2040,6 @@ module ts {
return (SyntaxKind.FirstPunctuation <= kind && kind <= SyntaxKind.LastPunctuation);
}
function isVisibleWithinClassDeclaration(symbol: Symbol, containingClass: Declaration): boolean {
var declaration = symbol.declarations && symbol.declarations[0];
if (declaration && (declaration.flags & NodeFlags.Private)) {
var declarationClass = getAncestor(declaration, SyntaxKind.ClassDeclaration);
return containingClass === declarationClass;
}
return true;
}
function filterContextualMembersList(contextualMemberSymbols: Symbol[], existingMembers: Declaration[]): Symbol[] {
if (!existingMembers || existingMembers.length === 0) {
return contextualMemberSymbols;
@@ -2130,7 +2123,7 @@ module ts {
}
// TODO: this is a hack for now, we need a proper walking mechanism to verify that we have the correct node
var mappedNode = getNodeAtPosition(sourceFile, TypeScript.end(node) - 1);
var mappedNode = getTouchingToken(sourceFile, TypeScript.end(node) - 1);
if (isPunctuation(mappedNode.kind)) {
mappedNode = mappedNode.parent;
}
@@ -2150,15 +2143,20 @@ module ts {
// Right of dot member completion list
if (isRightOfDot) {
var symbols: Symbol[] = [];
var containingClass = getAncestor(mappedNode, SyntaxKind.ClassDeclaration);
isMemberCompletion = true;
if (mappedNode.kind === SyntaxKind.Identifier || mappedNode.kind === SyntaxKind.QualifiedName || mappedNode.kind === SyntaxKind.PropertyAccess) {
var symbol = typeInfoResolver.getSymbolInfo(mappedNode);
// This is an alias, follow what it aliases
if (symbol && symbol.flags & SymbolFlags.Import) {
symbol = typeInfoResolver.getAliasedSymbol(symbol);
}
if (symbol && symbol.flags & SymbolFlags.HasExports) {
// Extract module or enum members
forEachValue(symbol.exports, symbol => {
if (isVisibleWithinClassDeclaration(symbol, containingClass)) {
if (typeInfoResolver.isValidPropertyAccess(<PropertyAccess>(mappedNode.parent), symbol.name)) {
symbols.push(symbol);
}
});
@@ -2170,7 +2168,7 @@ module ts {
if (apparentType) {
// Filter private properties
forEach(apparentType.getApparentProperties(), symbol => {
if (isVisibleWithinClassDeclaration(symbol, containingClass)) {
if (typeInfoResolver.isValidPropertyAccess(<PropertyAccess>(mappedNode.parent), symbol.name)) {
symbols.push(symbol);
}
});
@@ -2205,7 +2203,7 @@ module ts {
else {
isMemberCompletion = false;
/// TODO filter meaning based on the current context
var symbolMeanings = SymbolFlags.Type | SymbolFlags.Value | SymbolFlags.Namespace;
var symbolMeanings = SymbolFlags.Type | SymbolFlags.Value | SymbolFlags.Namespace | SymbolFlags.Import;
var symbols = typeInfoResolver.getSymbolsInScope(mappedNode, symbolMeanings);
getCompletionEntriesFromSymbols(symbols, activeCompletionSession);
@@ -2303,6 +2301,7 @@ module ts {
if (flags & SymbolFlags.Constructor) return ScriptElementKind.constructorImplementationElement;
if (flags & SymbolFlags.TypeParameter) return ScriptElementKind.typeParameterElement;
if (flags & SymbolFlags.EnumMember) return ScriptElementKind.variableElement;
if (flags & SymbolFlags.Import) return ScriptElementKind.alias;
return ScriptElementKind.unknown;
}
@@ -2368,7 +2367,7 @@ module ts {
fileName = TypeScript.switchToForwardSlashes(fileName);
var sourceFile = getSourceFile(fileName);
var node = getNodeAtPosition(sourceFile, position);
var node = getTouchingPropertyName(sourceFile, position);
if (!node) {
return undefined;
}
@@ -2476,7 +2475,7 @@ module ts {
fileName = TypeScript.switchToForwardSlashes(fileName);
var sourceFile = getSourceFile(fileName);
var node = getNodeAtPosition(sourceFile, position);
var node = getTouchingWord(sourceFile, position);
if (!node) {
return undefined;
}
@@ -2559,7 +2558,7 @@ module ts {
filename = TypeScript.switchToForwardSlashes(filename);
var sourceFile = getSourceFile(filename);
var node = getNodeAtPosition(sourceFile, position);
var node = getTouchingPropertyName(sourceFile, position);
if (!node) {
return undefined;
}
@@ -2623,7 +2622,7 @@ module ts {
filename = TypeScript.switchToForwardSlashes(filename);
var sourceFile = getSourceFile(filename);
var node = getNodeAtPosition(sourceFile, position);
var node = getTouchingWord(sourceFile, position);
if (!node) {
return undefined;
}
@@ -2645,6 +2644,11 @@ module ts {
return getReturnOccurrences(<ReturnStatement>node.parent);
}
break;
case SyntaxKind.ThrowKeyword:
if (hasKind(node.parent, SyntaxKind.ThrowStatement)) {
return getThrowOccurrences(<ThrowStatement>node.parent);
}
break;
case SyntaxKind.TryKeyword:
case SyntaxKind.CatchKeyword:
case SyntaxKind.FinallyKeyword:
@@ -2685,6 +2689,11 @@ module ts {
return getConstructorOccurrences(<ConstructorDeclaration>node.parent);
}
break;
case SyntaxKind.GetKeyword:
case SyntaxKind.SetKeyword:
if (hasKind(node.parent, SyntaxKind.GetAccessor) || hasKind(node.parent, SyntaxKind.SetAccessor)) {
return getGetAndSetOccurrences(<AccessorDeclaration>node.parent);
}
}
return undefined;
@@ -2762,12 +2771,108 @@ module ts {
}
var keywords: Node[] = []
forEachReturnStatement(<Block>(<FunctionDeclaration>func).body, returnStatement => {
forEachReturnStatement(<Block>func.body, returnStatement => {
pushKeywordIf(keywords, returnStatement.getFirstToken(), SyntaxKind.ReturnKeyword);
});
// Include 'throw' statements that do not occur within a try block.
forEach(aggregateOwnedThrowStatements(func.body), throwStatement => {
pushKeywordIf(keywords, throwStatement.getFirstToken(), SyntaxKind.ThrowKeyword);
});
return map(keywords, getReferenceEntryFromNode);
}
function getThrowOccurrences(throwStatement: ThrowStatement) {
var owner = getThrowStatementOwner(throwStatement);
if (!owner) {
return undefined;
}
var keywords: Node[] = [];
forEach(aggregateOwnedThrowStatements(owner), throwStatement => {
pushKeywordIf(keywords, throwStatement.getFirstToken(), SyntaxKind.ThrowKeyword);
});
// If the "owner" is a function, then we equate 'return' and 'throw' statements in their
// ability to "jump out" of the function, and include occurrences for both.
if (owner.kind === SyntaxKind.FunctionBlock) {
forEachReturnStatement(<Block>owner, returnStatement => {
pushKeywordIf(keywords, returnStatement.getFirstToken(), SyntaxKind.ReturnKeyword);
});
}
return map(keywords, getReferenceEntryFromNode);
}
/**
* Aggregates all throw-statements within this node *without* crossing
* into function boundaries and try-blocks with catch-clauses.
*/
function aggregateOwnedThrowStatements(node: Node): ThrowStatement[] {
var statementAccumulator: ThrowStatement[] = []
aggregate(node);
return statementAccumulator;
function aggregate(node: Node): void {
if (node.kind === SyntaxKind.ThrowStatement) {
statementAccumulator.push(<ThrowStatement>node);
}
else if (node.kind === SyntaxKind.TryStatement) {
var tryStatement = <TryStatement>node;
if (tryStatement.catchBlock) {
aggregate(tryStatement.catchBlock);
}
else {
// Exceptions thrown within a try block lacking a catch clause
// are "owned" in the current context.
aggregate(tryStatement.tryBlock);
}
if (tryStatement.finallyBlock) {
aggregate(tryStatement.finallyBlock);
}
}
// Do not cross function boundaries.
else if (!isAnyFunction(node)) {
forEachChild(node, aggregate);
}
};
}
/**
* For lack of a better name, this function takes a throw statement and returns the
* nearest ancestor that is a try-block (whose try statement has a catch clause),
* function-block, or source file.
*/
function getThrowStatementOwner(throwStatement: ThrowStatement): Node {
var child: Node = throwStatement;
while (child.parent) {
var parent = child.parent;
if (parent.kind === SyntaxKind.FunctionBlock || parent.kind === SyntaxKind.SourceFile) {
return parent;
}
// A throw-statement is only owned by a try-statement if the try-statement has
// a catch clause, and if the throw-statement occurs within the try block.
if (parent.kind === SyntaxKind.TryStatement) {
var tryStatement = <TryStatement>parent;
if (tryStatement.tryBlock === child && tryStatement.catchBlock) {
return child;
}
}
child = parent;
}
return undefined;
}
function getTryCatchFinallyOccurrences(tryStatement: TryStatement): ReferenceEntry[] {
var keywords: Node[] = [];
@@ -2919,6 +3024,23 @@ module ts {
return map(keywords, getReferenceEntryFromNode);
}
function getGetAndSetOccurrences(accessorDeclaration: AccessorDeclaration): ReferenceEntry[] {
var keywords: Node[] = [];
tryPushAccessorKeyword(accessorDeclaration.symbol, SyntaxKind.GetAccessor);
tryPushAccessorKeyword(accessorDeclaration.symbol, SyntaxKind.SetAccessor);
return map(keywords, getReferenceEntryFromNode);
function tryPushAccessorKeyword(accessorSymbol: Symbol, accessorKind: SyntaxKind): void {
var accessor = getDeclarationOfKind(accessorSymbol, accessorKind);
if (accessor) {
forEach(accessor.getChildren(), child => pushKeywordIf(keywords, child, SyntaxKind.GetKeyword, SyntaxKind.SetKeyword));
}
}
}
// returns true if 'node' is defined and has a matching 'kind'.
function hasKind(node: Node, kind: SyntaxKind) {
return node !== undefined && node.kind === kind;
@@ -2945,7 +3067,7 @@ module ts {
filename = TypeScript.switchToForwardSlashes(filename);
var sourceFile = getSourceFile(filename);
var node = getNodeAtPosition(sourceFile, position);
var node = getTouchingPropertyName(sourceFile, position);
if (!node) {
return undefined;
}
@@ -3128,7 +3250,7 @@ module ts {
forEach(possiblePositions, position => {
cancellationToken.throwIfCancellationRequested();
var node = getNodeAtPosition(sourceFile, position);
var node = getTouchingWord(sourceFile, position);
if (!node || node.getWidth() !== labelName.length) {
return;
}
@@ -3184,7 +3306,7 @@ module ts {
forEach(possiblePositions, position => {
cancellationToken.throwIfCancellationRequested();
var referenceLocation = getNodeAtPosition(sourceFile, position);
var referenceLocation = getTouchingPropertyName(sourceFile, position);
if (!isValidReferencePosition(referenceLocation, searchText)) {
return;
}
@@ -3236,7 +3358,7 @@ module ts {
forEach(possiblePositions, position => {
cancellationToken.throwIfCancellationRequested();
var node = getNodeAtPosition(sourceFile, position);
var node = getTouchingWord(sourceFile, position);
if (!node || node.kind !== SyntaxKind.SuperKeyword) {
return;
@@ -3302,7 +3424,7 @@ module ts {
forEach(possiblePositions, position => {
cancellationToken.throwIfCancellationRequested();
var node = getNodeAtPosition(sourceFile, position);
var node = getTouchingWord(sourceFile, position);
if (!node || node.kind !== SyntaxKind.ThisKeyword) {
return;
}
@@ -4116,7 +4238,7 @@ module ts {
var sourceFile = getCurrentSourceFile(filename);
var result: TypeScript.TextSpan[] = [];
var token = getTokenAtPosition(sourceFile, position);
var token = getTouchingToken(sourceFile, position);
if (token.getStart(sourceFile) === position) {
var matchKind = getMatchingTokenKind(token);
@@ -4400,7 +4522,7 @@ module ts {
fileName = TypeScript.switchToForwardSlashes(fileName);
var sourceFile = getSourceFile(fileName);
var node = getNodeAtPosition(sourceFile, position);
var node = getTouchingWord(sourceFile, position);
// Can only rename an identifier.
if (node && node.kind === SyntaxKind.Identifier) {
@@ -4486,26 +4608,58 @@ module ts {
/// If we consider every slash token to be a regex, we could be missing cases like "1/2/3", where
/// we have a series of divide operator. this list allows us to be more accurate by ruling out
/// locations where a regexp cannot exist.
var noRegexTable: boolean[];
if (!noRegexTable) {
noRegexTable = [];
noRegexTable[SyntaxKind.Identifier] = true;
noRegexTable[SyntaxKind.StringLiteral] = true;
noRegexTable[SyntaxKind.NumericLiteral] = true;
noRegexTable[SyntaxKind.RegularExpressionLiteral] = true;
noRegexTable[SyntaxKind.ThisKeyword] = true;
noRegexTable[SyntaxKind.PlusPlusToken] = true;
noRegexTable[SyntaxKind.MinusMinusToken] = true;
noRegexTable[SyntaxKind.CloseParenToken] = true;
noRegexTable[SyntaxKind.CloseBracketToken] = true;
noRegexTable[SyntaxKind.CloseBraceToken] = true;
noRegexTable[SyntaxKind.TrueKeyword] = true;
noRegexTable[SyntaxKind.FalseKeyword] = true;
var noRegexTable: boolean[] = [];
noRegexTable[SyntaxKind.Identifier] = true;
noRegexTable[SyntaxKind.StringLiteral] = true;
noRegexTable[SyntaxKind.NumericLiteral] = true;
noRegexTable[SyntaxKind.RegularExpressionLiteral] = true;
noRegexTable[SyntaxKind.ThisKeyword] = true;
noRegexTable[SyntaxKind.PlusPlusToken] = true;
noRegexTable[SyntaxKind.MinusMinusToken] = true;
noRegexTable[SyntaxKind.CloseParenToken] = true;
noRegexTable[SyntaxKind.CloseBracketToken] = true;
noRegexTable[SyntaxKind.CloseBraceToken] = true;
noRegexTable[SyntaxKind.TrueKeyword] = true;
noRegexTable[SyntaxKind.FalseKeyword] = true;
function isAccessibilityModifier(kind: SyntaxKind) {
switch (kind) {
case SyntaxKind.PublicKeyword:
case SyntaxKind.PrivateKeyword:
case SyntaxKind.ProtectedKeyword:
return true;
}
return false;
}
/** Returns true if 'keyword2' can legally follow 'keyword1' in any language construct. */
function canFollow(keyword1: SyntaxKind, keyword2: SyntaxKind) {
if (isAccessibilityModifier(keyword1)) {
if (keyword2 === SyntaxKind.GetKeyword ||
keyword2 === SyntaxKind.SetKeyword ||
keyword2 === SyntaxKind.ConstructorKeyword ||
keyword2 === SyntaxKind.StaticKeyword) {
// Allow things like "public get", "public constructor" and "public static".
// These are all legal.
return true;
}
// Any other keyword following "public" is actually an identifier an not a real
// keyword.
return false;
}
// Assume any other keyword combination is legal. This can be refined in the future
// if there are more cases we want the classifier to be better at.
return true;
}
function getClassificationsForLine(text: string, lexState: EndOfLineState): ClassificationResult {
var offset = 0;
var lastTokenOrCommentEnd = 0;
var token = SyntaxKind.Unknown;
var lastNonTriviaToken = SyntaxKind.Unknown;
// If we're in a string literal, then prepend: "\
@@ -4535,8 +4689,6 @@ module ts {
entries: []
};
var token = SyntaxKind.Unknown;
do {
token = scanner.scan();
@@ -4549,6 +4701,13 @@ module ts {
else if (lastNonTriviaToken === SyntaxKind.DotToken && isKeyword(token)) {
token = SyntaxKind.Identifier;
}
else if (isKeyword(lastNonTriviaToken) && isKeyword(token) && !canFollow(lastNonTriviaToken, token)) {
// We have two keywords in a row. Only treat the second as a keyword if
// it's a sequence that could legally occur in the language. Otherwise
// treat it as an identifier. This way, if someone writes "private var"
// we recognize that 'var' is actually an identifier here.
token = SyntaxKind.Identifier;
}
lastNonTriviaToken = token;
}
+69 -67
View File
@@ -172,15 +172,15 @@ module ts.SignatureHelp {
return undefined;
}
var argumentList = getContainingArgumentList(startingToken);
var argumentInfo = getContainingArgumentInfo(startingToken);
cancellationToken.throwIfCancellationRequested();
// Semantic filtering of signature help
if (!argumentList) {
if (!argumentInfo) {
return undefined;
}
var call = <CallExpression>argumentList.parent;
var call = <CallExpression>argumentInfo.list.parent;
var candidates = <Signature[]>[];
var resolvedSignature = typeInfoResolver.getResolvedSignature(call, candidates);
cancellationToken.throwIfCancellationRequested();
@@ -189,13 +189,13 @@ module ts.SignatureHelp {
return undefined;
}
return createSignatureHelpItems(candidates, resolvedSignature, argumentList);
return createSignatureHelpItems(candidates, resolvedSignature, argumentInfo);
/**
* If node is an argument, returns its index in the argument list.
* If not, returns -1.
*/
function getImmediatelyContainingArgumentList(node: Node): Node {
function getImmediatelyContainingArgumentInfo(node: Node): ListItemInfo {
if (node.parent.kind !== SyntaxKind.CallExpression && node.parent.kind !== SyntaxKind.NewExpression) {
return undefined;
}
@@ -216,10 +216,14 @@ module ts.SignatureHelp {
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
// Find the list that starts right *after* the < or ( token.
// If the user has just opened a list, consider this item 0.
var list = getChildListThatStartsWithOpenerToken(parent, node, sourceFile);
Debug.assert(list);
return list;
return {
list: list,
listItemIndex: 0
};
}
if (node.kind === SyntaxKind.GreaterThanToken
@@ -228,18 +232,18 @@ module ts.SignatureHelp {
return undefined;
}
return findContainingList(node);
return findListItemInfo(node);
}
function getContainingArgumentList(node: Node): Node {
function getContainingArgumentInfo(node: Node): ListItemInfo {
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;
var argumentInfo = getImmediatelyContainingArgumentInfo(n);
if (argumentInfo) {
return argumentInfo;
}
@@ -248,7 +252,35 @@ module ts.SignatureHelp {
return undefined;
}
function createSignatureHelpItems(candidates: Signature[], bestSignature: Signature, argumentListOrTypeArgumentList: Node): SignatureHelpItems {
/**
* The selectedItemIndex could be negative for several reasons.
* 1. There are too many arguments for all of the overloads
* 2. None of the overloads were type compatible
* The solution here is to try to pick the best overload by picking
* either the first one that has an appropriate number of parameters,
* or the one with the most parameters.
*/
function selectBestInvalidOverloadIndex(candidates: Signature[], argumentCount: number): number {
var maxParamsSignatureIndex = -1;
var maxParams = -1;
for (var i = 0; i < candidates.length; i++) {
var candidate = candidates[i];
if (candidate.hasRestParameter || candidate.parameters.length >= argumentCount) {
return i;
}
if (candidate.parameters.length > maxParams) {
maxParams = candidate.parameters.length;
maxParamsSignatureIndex = i;
}
}
return maxParamsSignatureIndex;
}
function createSignatureHelpItems(candidates: Signature[], bestSignature: Signature, argumentInfoOrTypeArgumentInfo: ListItemInfo): SignatureHelpItems {
var argumentListOrTypeArgumentList = argumentInfoOrTypeArgumentInfo.list;
var items: SignatureHelpItem[] = map(candidates, candidateSignature => {
var parameters = candidateSignature.parameters;
var parameterHelpItems: SignatureHelpParameter[] = parameters.length === 0 ? emptyArray : map(parameters, p => {
@@ -321,11 +353,6 @@ module ts.SignatureHelp {
};
});
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,
//
@@ -338,63 +365,38 @@ module ts.SignatureHelp {
var applicableSpanEnd = skipTrivia(sourceFile.text, argumentListOrTypeArgumentList.end, /*stopAfterLineBreak*/ false);
var applicableSpan = new TypeScript.TextSpan(applicableSpanStart, applicableSpanEnd - applicableSpanStart);
var state = getSignatureHelpCurrentArgumentState(sourceFile, position, applicableSpanStart);
// The listItemIndex we got back includes commas. Our goal is to return the index of the proper
// item (not including commas). Here are some examples:
// 1. foo(a, b, c $) -> the listItemIndex is 4, we want to return 2
// 2. foo(a, b, $ c) -> listItemIndex is 3, we want to return 2
// 3. foo($a) -> listItemIndex is 0, we want to return 0
//
// In general, we want to subtract the number of commas before the current index.
// But if we are on a comma, we also want to pretend we are on the argument *following*
// the comma. That amounts to taking the ceiling of half the index.
var argumentIndex = (argumentInfoOrTypeArgumentInfo.listItemIndex + 1) >> 1;
// argumentCount is the number of commas plus one, unless the list is completely empty,
// in which case there are 0.
var argumentCount = argumentListOrTypeArgumentList.getChildCount() === 0
? 0
: 1 + countWhere(argumentListOrTypeArgumentList.getChildren(), arg => arg.kind === SyntaxKind.CommaToken);
var selectedItemIndex = candidates.indexOf(bestSignature);
if (selectedItemIndex < 0) {
selectedItemIndex = selectBestInvalidOverloadIndex(candidates, argumentCount);
}
return {
items: items,
applicableSpan: applicableSpan,
selectedItemIndex: selectedItemIndex,
argumentIndex: state.argumentIndex,
argumentCount: state.argumentCount
argumentIndex: argumentIndex,
argumentCount: argumentCount
};
}
}
function getSignatureHelpCurrentArgumentState(sourceFile: SourceFile, position: number, applicableSpanStart: number): { argumentIndex: number; argumentCount: number } {
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 { argumentIndex: 0, argumentCount: 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 { argumentIndex: argumentIndex, argumentCount: argumentCount };
}
function getChildListThatStartsWithOpenerToken(parent: Node, openerToken: Node, sourceFile: SourceFile): Node {
var children = parent.getChildren(sourceFile);
var indexOfOpenerToken = children.indexOf(openerToken);
+58 -26
View File
@@ -50,34 +50,54 @@ module ts {
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;
}
/* Gets the token whose text has range [start, end) and
* position >= start and (position < end or (position === end && token is keyword or identifier))
*/
export function getTouchingWord(sourceFile: SourceFile, position: number): Node {
return getTouchingToken(sourceFile, position, isWord);
}
/** Get the token whose text contains the position, or the containing node. */
export function getNodeAtPosition(sourceFile: SourceFile, position: number) {
/* Gets the token whose text has range [start, end) and position >= start
* and (position < end or (position === end && token is keyword or identifier or numeric\string litera))
*/
export function getTouchingPropertyName(sourceFile: SourceFile, position: number): Node {
return getTouchingToken(sourceFile, position, isPropertyName);
}
/** Returns the token if position is in [start, end) or if position === end and includeItemAtEndPosition(token) === true */
export function getTouchingToken(sourceFile: SourceFile, position: number, includeItemAtEndPosition?: (n: Node) => boolean): Node {
return getTokenAtPositionWorker(sourceFile, position, /*allowPositionInLeadingTrivia*/ false, includeItemAtEndPosition);
}
/** Returns a token if position is in [start-of-leading-trivia, end) */
export function getTokenAtPosition(sourceFile: SourceFile, position: number): Node {
return getTokenAtPositionWorker(sourceFile, position, /*allowPositionInLeadingTrivia*/ true, /*includeItemAtEndPosition*/ undefined);
}
/** Get the token whose text contains the position */
function getTokenAtPositionWorker(sourceFile: SourceFile, position: number, allowPositionInLeadingTrivia: boolean, includeItemAtEndPosition: (n: Node) => boolean): Node {
var current: Node = sourceFile;
outer: while (true) {
// find the child that has this
for (var i = 0, n = current.getChildCount(); i < n; i++) {
if (isToken(current)) {
// exit early
return current;
}
// find the child that contains 'position'
for (var i = 0, n = current.getChildCount(sourceFile); i < n; i++) {
var child = current.getChildAt(i);
if (child.getStart() <= position && position < child.getEnd()) {
current = child;
continue outer;
var start = allowPositionInLeadingTrivia ? child.getFullStart() : child.getStart(sourceFile);
if (start <= position) {
if (position < child.getEnd()) {
current = child;
continue outer;
}
else if (includeItemAtEndPosition && child.getEnd() === position) {
var previousToken = findPrecedingToken(position, sourceFile, child);
if (previousToken && includeItemAtEndPosition(previousToken)) {
return previousToken;
}
}
}
}
return current;
@@ -130,8 +150,8 @@ module ts {
}
}
export function findPrecedingToken(position: number, sourceFile: SourceFile): Node {
return find(sourceFile);
export function findPrecedingToken(position: number, sourceFile: SourceFile, startNode?: Node): Node {
return find(startNode || sourceFile);
function findRightmostToken(n: Node): Node {
if (isToken(n)) {
@@ -167,7 +187,7 @@ module ts {
}
}
Debug.assert(n.kind === SyntaxKind.SourceFile);
Debug.assert(startNode || 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.
@@ -205,4 +225,16 @@ module ts {
function isToken(n: Node): boolean {
return n.kind >= SyntaxKind.FirstToken && n.kind <= SyntaxKind.LastToken;
}
function isKeyword(n: Node): boolean {
return n.kind >= SyntaxKind.FirstKeyword && n.kind <= SyntaxKind.LastKeyword;
}
function isWord(n: Node): boolean {
return n.kind === SyntaxKind.Identifier || isKeyword(n);
}
function isPropertyName(n: Node): boolean {
return n.kind === SyntaxKind.StringLiteral || n.kind === SyntaxKind.NumericLiteral || isWord(n);
}
}