Skip to content

Commit a6497d8

Browse files
Add support for syntactic classification.
Tests pending.
1 parent b9ebb36 commit a6497d8

File tree

4 files changed

+179
-4
lines changed

4 files changed

+179
-4
lines changed

src/compiler/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ module ts {
221221
FirstTypeNode = TypeReference,
222222
LastTypeNode = ArrayType,
223223
FirstPunctuation = OpenBraceToken,
224-
LastPunctuation = CaretEqualsToken
224+
LastPunctuation = CaretEqualsToken,
225225
}
226226

227227
export enum NodeFlags {

src/services/services.ts

Lines changed: 166 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ module ts {
7575

7676
var scanner: Scanner = createScanner(ScriptTarget.ES5);
7777

78-
var emptyArray: any [] = [];
78+
var emptyArray: any[] = [];
7979

8080
function createNode(kind: SyntaxKind, pos: number, end: number, flags: NodeFlags, parent?: Node): NodeObject {
8181
var node = <NodeObject> new (getNodeConstructor(kind))();
@@ -259,7 +259,7 @@ module ts {
259259
getProperty(propertyName: string): Symbol {
260260
return this.checker.getPropertyOfType(this, propertyName);
261261
}
262-
getApparentProperties(): Symbol[]{
262+
getApparentProperties(): Symbol[] {
263263
return this.checker.getAugmentedPropertiesOfApparentType(this);
264264
}
265265
getCallSignatures(): Signature[] {
@@ -302,7 +302,7 @@ module ts {
302302
}
303303
}
304304

305-
var incrementalParse: IncrementalParse = TypeScript.IncrementalParser.parse;
305+
var incrementalParse: IncrementalParse = TypeScript.IncrementalParser.parse;
306306

307307
class SourceFileObject extends NodeObject implements SourceFile {
308308
public filename: string;
@@ -430,6 +430,8 @@ module ts {
430430
getSemanticDiagnostics(fileName: string): Diagnostic[];
431431
getCompilerOptionsDiagnostics(): Diagnostic[];
432432

433+
getSyntacticClassifications(fileName: string, span: TypeScript.TextSpan): ClassifiedSpan[];
434+
433435
getCompletionsAtPosition(fileName: string, position: number, isMemberCompletion: boolean): CompletionInfo;
434436
getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails;
435437

@@ -467,6 +469,32 @@ module ts {
467469
dispose(): void;
468470
}
469471

472+
class ClassificationTypeNames {
473+
public static comment = "comment";
474+
public static identifier = "identifier";
475+
public static keyword = "keyword";
476+
public static numericLiteral = "number";
477+
public static operator = "operator";
478+
public static stringLiteral = "string";
479+
public static whiteSpace = "whitespace";
480+
public static text = "text";
481+
482+
public static punctuation = "punctuation";
483+
484+
public static className = "class name";
485+
public static enumName = "enum name";
486+
public static interfaceName = "interface name";
487+
public static moduleName = "module name";
488+
public static typeParameterName = "type parameter name";
489+
}
490+
491+
export class ClassifiedSpan {
492+
constructor(public textSpan: TypeScript.TextSpan,
493+
public classificationType: string) {
494+
495+
}
496+
}
497+
470498
export class NavigationBarItem {
471499
constructor(public text: string,
472500
public kind: string,
@@ -3124,6 +3152,140 @@ module ts {
31243152
return new TypeScript.Services.NavigationBarItemGetter().getItems(syntaxTree.sourceUnit());
31253153
}
31263154

3155+
function getSyntacticClassifications(fileName: string, span: TypeScript.TextSpan): ClassifiedSpan[] {
3156+
// doesn't use compiler - no need to synchronize with host
3157+
fileName = TypeScript.switchToForwardSlashes(fileName);
3158+
var sourceFile = getCurrentSourceFile(fileName);
3159+
3160+
var result: ClassifiedSpan[] = [];
3161+
processElement(sourceFile.getSourceUnit());
3162+
3163+
return result;
3164+
3165+
function classifyTrivia(trivia: TypeScript.ISyntaxTrivia) {
3166+
if (span.intersectsWith(trivia.fullStart(), trivia.fullWidth())) {
3167+
result.push(new ClassifiedSpan(
3168+
new TypeScript.TextSpan(trivia.fullStart(), trivia.fullWidth()),
3169+
ClassificationTypeNames.comment));
3170+
}
3171+
}
3172+
3173+
function classifyTriviaList(trivia: TypeScript.ISyntaxTriviaList) {
3174+
for (var i = 0, n = trivia.count(); i < n; i++) {
3175+
classifyTrivia(trivia.syntaxTriviaAt(i));
3176+
}
3177+
}
3178+
3179+
function classifyToken(token: TypeScript.ISyntaxToken) {
3180+
if (token.hasLeadingComment()) {
3181+
classifyTriviaList(token.leadingTrivia());
3182+
}
3183+
3184+
if (TypeScript.width(token) > 0) {
3185+
var span = new TypeScript.TextSpan(TypeScript.start(token), TypeScript.width(token));
3186+
var type = classifyTokenType(token);
3187+
3188+
result.push(new ClassifiedSpan(span, type));
3189+
}
3190+
3191+
if (token.hasTrailingComment()) {
3192+
classifyTriviaList(token.trailingTrivia());
3193+
}
3194+
}
3195+
3196+
function classifyTokenType(token: TypeScript.ISyntaxToken): string {
3197+
var tokenKind = token.kind();
3198+
if (TypeScript.SyntaxFacts.isAnyKeyword(token.kind())) {
3199+
return ClassificationTypeNames.keyword;
3200+
}
3201+
3202+
// Special case < and > If they appear in a generic context they are punctation,
3203+
// not operators.
3204+
if (tokenKind === TypeScript.SyntaxKind.LessThanToken || tokenKind === TypeScript.SyntaxKind.GreaterThanToken) {
3205+
var tokenParentKind = token.parent.kind();
3206+
if (tokenParentKind === TypeScript.SyntaxKind.TypeArgumentList ||
3207+
tokenParentKind === TypeScript.SyntaxKind.TypeParameterList) {
3208+
3209+
return ClassificationTypeNames.punctuation;
3210+
}
3211+
}
3212+
3213+
if (TypeScript.SyntaxFacts.isBinaryExpressionOperatorToken(tokenKind) ||
3214+
TypeScript.SyntaxFacts.isPrefixUnaryExpressionOperatorToken(tokenKind)) {
3215+
return ClassificationTypeNames.operator;
3216+
}
3217+
else if (TypeScript.SyntaxFacts.isAnyPunctuation(tokenKind)) {
3218+
return ClassificationTypeNames.punctuation;
3219+
}
3220+
else if (tokenKind === TypeScript.SyntaxKind.NumericLiteral) {
3221+
return ClassificationTypeNames.numericLiteral;
3222+
}
3223+
else if (tokenKind === TypeScript.SyntaxKind.StringLiteral) {
3224+
return ClassificationTypeNames.stringLiteral;
3225+
}
3226+
else if (tokenKind === TypeScript.SyntaxKind.RegularExpressionLiteral) {
3227+
// TODO: we shoudl get another classification type for these literals.
3228+
return ClassificationTypeNames.stringLiteral;
3229+
}
3230+
else if (tokenKind === TypeScript.SyntaxKind.IdentifierName) {
3231+
var current: TypeScript.ISyntaxNodeOrToken = token;
3232+
var parent = token.parent;
3233+
while (parent.kind() === TypeScript.SyntaxKind.QualifiedName) {
3234+
current = parent;
3235+
parent = parent.parent;
3236+
}
3237+
3238+
switch (parent.kind()) {
3239+
case TypeScript.SyntaxKind.ClassDeclaration:
3240+
if ((<TypeScript.ClassDeclarationSyntax>parent).identifier === token) {
3241+
return ClassificationTypeNames.className;
3242+
}
3243+
return;
3244+
case TypeScript.SyntaxKind.TypeParameter:
3245+
if ((<TypeScript.TypeParameterSyntax>parent).identifier === token) {
3246+
return ClassificationTypeNames.typeParameterName;
3247+
}
3248+
return;
3249+
case TypeScript.SyntaxKind.InterfaceDeclaration:
3250+
if ((<TypeScript.InterfaceDeclarationSyntax>parent).identifier === token) {
3251+
return ClassificationTypeNames.interfaceName;
3252+
}
3253+
return;
3254+
case TypeScript.SyntaxKind.EnumDeclaration:
3255+
if ((<TypeScript.EnumDeclarationSyntax>parent).identifier === token) {
3256+
return ClassificationTypeNames.enumName;
3257+
}
3258+
return;
3259+
case TypeScript.SyntaxKind.ModuleDeclaration:
3260+
if ((<TypeScript.ModuleDeclarationSyntax>parent).name === current) {
3261+
return ClassificationTypeNames.moduleName;
3262+
}
3263+
return;
3264+
default:
3265+
return ClassificationTypeNames.text;
3266+
}
3267+
}
3268+
}
3269+
3270+
function processElement(element: TypeScript.ISyntaxElement) {
3271+
// Ignore nodes that don't intersect the original span to classify.
3272+
if (!TypeScript.isShared(element) && span.intersectsWith(TypeScript.fullStart(element), TypeScript.fullWidth(element))) {
3273+
for (var i = 0, n = TypeScript.childCount(element); i < n; i++) {
3274+
var child = TypeScript.childAt(element, i);
3275+
if (child) {
3276+
if (TypeScript.isToken(child)) {
3277+
classifyToken(<TypeScript.ISyntaxToken>child);
3278+
}
3279+
else {
3280+
// Recurse into our child nodes.
3281+
processElement(child);
3282+
}
3283+
}
3284+
}
3285+
}
3286+
}
3287+
}
3288+
31273289
function getOutliningSpans(filename: string): OutliningSpan[] {
31283290
// doesn't use compiler - no need to synchronize with host
31293291
filename = TypeScript.switchToForwardSlashes(filename);
@@ -3371,6 +3533,7 @@ module ts {
33713533
getSyntacticDiagnostics: getSyntacticDiagnostics,
33723534
getSemanticDiagnostics: getSemanticDiagnostics,
33733535
getCompilerOptionsDiagnostics: getCompilerOptionsDiagnostics,
3536+
getSyntacticClassifications: getSyntacticClassifications,
33743537
getCompletionsAtPosition: getCompletionsAtPosition,
33753538
getCompletionEntryDetails: getCompletionEntryDetails,
33763539
getTypeAtPosition: getTypeAtPosition,

src/services/shims.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ module ts {
8080
getSemanticDiagnostics(fileName: string): string;
8181
getCompilerOptionsDiagnostics(): string;
8282

83+
getSyntacticClassifications(fileName: string, start: number, length: number): string;
84+
8385
getCompletionsAtPosition(fileName: string, position: number, isMemberCompletion: boolean): string;
8486
getCompletionEntryDetails(fileName: string, position: number, entryName: string): string;
8587

@@ -477,6 +479,15 @@ module ts {
477479
};
478480
}
479481

482+
public getSyntacticClassifications(fileName: string, start: number, length: number): string {
483+
return this.forwardJSONCall(
484+
"getSyntacticClassifications('" + fileName + "', " + start + ", " + length + ")",
485+
() => {
486+
var classifications = this.languageService.getSyntacticClassifications(fileName, new TypeScript.TextSpan(start, length));
487+
return classifications;
488+
});
489+
}
490+
480491
public getSyntacticDiagnostics(fileName: string): string {
481492
return this.forwardJSONCall(
482493
"getSyntacticDiagnostics('" + fileName + "')",

src/services/text/textSpan.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
///<reference path='references.ts' />
22

33
module TypeScript {
4+
45
export interface ISpan {
56
start(): number;
67
end(): number;

0 commit comments

Comments
 (0)