diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index c571a7d9f94..0bcea9367e4 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -1428,6 +1428,46 @@ module FourSlash { Harness.IO.log(this.getNameOrDottedNameSpan(pos)); } + 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); + } + + for (var i = 0; i < expected.length; i++) { + var expectedClassification = expected[i]; + var actualClassification = actual[i]; + + var expectedType: string = (ts.ClassificationTypeNames)[expectedClassification.classificationType]; + if (expectedType !== actualClassification.classificationType) { + throw new Error('verifySyntacticClassification failed - expected classifications type to be ' + + expectedType + ', but was ' + + actualClassification.classificationType); + } + + 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 ' + + expectedClassification.text + ', but was ' + + actualText); + } + } + } + + public verifySemanticClassifications(expected: { classificationType: string; text: string }[]) { + var actual = this.languageService.getSemanticClassifications(this.activeFile.fileName, + new TypeScript.TextSpan(0, this.activeFile.content.length)); + + this.verifyClassifications(expected, actual); + } + + public verifySyntacticClassifications(expected: { classificationType: string; text: string }[]) { + var actual = this.languageService.getSyntacticClassifications(this.activeFile.fileName, + new TypeScript.TextSpan(0, this.activeFile.content.length)); + + this.verifyClassifications(expected, actual); + } + public verifyOutliningSpans(spans: TextSpan[]) { this.taoInvalidReason = 'verifyOutliningSpans NYI'; diff --git a/src/services/services.ts b/src/services/services.ts index 6385f68c7ae..90170ad0b3f 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -470,7 +470,7 @@ module ts { dispose(): void; } - class ClassificationTypeNames { + export class ClassificationTypeNames { public static comment = "comment"; public static identifier = "identifier"; public static keyword = "keyword"; @@ -3185,14 +3185,16 @@ module ts { } function processNode(node: Node) { - if (span.intersectsWith(node.getStart(), node.getWidth())) { - if (node.kind === SyntaxKind.Identifier && node.getWidth()) { + // Only walk into nodes that intersect the requested span. + if (node && span.intersectsWith(node.getStart(), node.getWidth())) { + if (node.kind === SyntaxKind.Identifier && node.getWidth() > 0) { var symbol = typeInfoResolver.getSymbolInfo(node); if (symbol) { - var span = new TypeScript.TextSpan(node.getStart(), node.getWidth()); var type = classifySymbol(symbol); if (type) { - result.push(new ClassifiedSpan(span, type)); + result.push(new ClassifiedSpan( + new TypeScript.TextSpan(node.getStart(), node.getWidth()), + type)); } } } @@ -3213,7 +3215,7 @@ module ts { return result; function classifyTrivia(trivia: TypeScript.ISyntaxTrivia) { - if (span.intersectsWith(trivia.fullStart(), trivia.fullWidth())) { + if (trivia.isComment() && span.intersectsWith(trivia.fullStart(), trivia.fullWidth())) { result.push(new ClassifiedSpan( new TypeScript.TextSpan(trivia.fullStart(), trivia.fullWidth()), ClassificationTypeNames.comment)); @@ -3232,10 +3234,11 @@ module ts { } if (TypeScript.width(token) > 0) { - var span = new TypeScript.TextSpan(TypeScript.start(token), TypeScript.width(token)); var type = classifyTokenType(token); if (type) { - result.push(new ClassifiedSpan(span, type)); + result.push(new ClassifiedSpan( + new TypeScript.TextSpan(TypeScript.start(token), TypeScript.width(token)), + type)); } } @@ -3287,6 +3290,11 @@ module ts { } switch (parent.kind()) { + case TypeScript.SyntaxKind.SimplePropertyAssignment: + if ((parent).propertyName === token) { + return ClassificationTypeNames.identifier; + } + return; case TypeScript.SyntaxKind.ClassDeclaration: if ((parent).identifier === token) { return ClassificationTypeNames.className; diff --git a/src/services/text/textSpan.ts b/src/services/text/textSpan.ts index d719e244010..999070a73b4 100644 --- a/src/services/text/textSpan.ts +++ b/src/services/text/textSpan.ts @@ -1,6 +1,7 @@ /// module TypeScript { + export interface ISpan { start(): number; end(): number; diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index b4080d12e2f..df4e3b31730 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -382,6 +382,10 @@ module FourSlashInterface { public completionEntryDetailIs(entryName: string, type: string, docComment?: string, fullSymbolName?: string, kind?: string) { FourSlash.currentTestState.verifyCompletionEntryDetails(entryName, type, docComment, fullSymbolName, kind); } + + public syntacticClassificationsAre(...classifications: { classificationType: string; text: string }[]) { + FourSlash.currentTestState.verifySyntacticClassifications(classifications); + } } export class edit { @@ -524,6 +528,64 @@ module FourSlashInterface { FourSlash.currentTestState.cancellationToken.setCancelled(numberOfCalls); } } + + export class classification { + public static comment(text: string): { classificationType: string; text: string } { + return { classificationType: "comment", text: text }; + } + + public static identifier(text: string): { classificationType: string; text: string } { + return { classificationType: "identifier", text: text }; + } + + public static keyword(text: string): { classificationType: string; text: string } { + return { classificationType: "keyword", text: text }; + } + + public static numericLiteral(text: string): { classificationType: string; text: string } { + return { classificationType: "numericLiteral", text: text }; + } + + public static operator(text: string): { classificationType: string; text: string } { + return { classificationType: "operator", text: text }; + } + + public static stringLiteral(text: string): { classificationType: string; text: string } { + return { classificationType: "stringLiteral", text: text }; + } + + public static whiteSpace(text: string): { classificationType: string; text: string } { + return { classificationType: "whiteSpace", text: text }; + } + + public static text(text: string): { classificationType: string; text: string } { + return { classificationType: "text", text: text }; + } + + public static punctuation(text: string): { classificationType: string; text: string } { + return { classificationType: "punctuation", text: text }; + } + + public static className(text: string): { classificationType: string; text: string } { + return { classificationType: "className", text: text }; + } + + public static enumName(text: string): { classificationType: string; text: string } { + return { classificationType: "enumName", text: text }; + } + + public static interfaceName(text: string): { classificationType: string; text: string } { + return { classificationType: "interfaceName", text: text }; + } + + public static moduleName(text: string): { classificationType: string; text: string } { + return { classificationType: "moduleName", text: text }; + } + + public static typeParameterName(text: string): { classificationType: string; text: string } { + return { classificationType: "typeParameterName", text: text }; + } + } } module fs { @@ -547,3 +609,4 @@ var debug = new FourSlashInterface.debug(); var format = new FourSlashInterface.format(); var diagnostics = new FourSlashInterface.diagnostics(); var cancellation = new FourSlashInterface.cancellation(); +var classification = FourSlashInterface.classification;