mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-19 10:41:56 -05:00
Merge pull request #672 from Microsoft/getSyntacticClassifications
Add support for syntactic classification.
This commit is contained in:
@@ -222,7 +222,7 @@ module ts {
|
||||
FirstTypeNode = TypeReference,
|
||||
LastTypeNode = TupleType,
|
||||
FirstPunctuation = OpenBraceToken,
|
||||
LastPunctuation = CaretEqualsToken
|
||||
LastPunctuation = CaretEqualsToken,
|
||||
}
|
||||
|
||||
export enum NodeFlags {
|
||||
|
||||
@@ -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 = (<any>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';
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ module ts {
|
||||
|
||||
var scanner: Scanner = createScanner(ScriptTarget.ES5);
|
||||
|
||||
var emptyArray: any [] = [];
|
||||
var emptyArray: any[] = [];
|
||||
|
||||
function createNode(kind: SyntaxKind, pos: number, end: number, flags: NodeFlags, parent?: Node): NodeObject {
|
||||
var node = <NodeObject> new (getNodeConstructor(kind))();
|
||||
@@ -259,7 +259,7 @@ module ts {
|
||||
getProperty(propertyName: string): Symbol {
|
||||
return this.checker.getPropertyOfType(this, propertyName);
|
||||
}
|
||||
getApparentProperties(): Symbol[]{
|
||||
getApparentProperties(): Symbol[] {
|
||||
return this.checker.getAugmentedPropertiesOfApparentType(this);
|
||||
}
|
||||
getCallSignatures(): Signature[] {
|
||||
@@ -302,7 +302,7 @@ module ts {
|
||||
}
|
||||
}
|
||||
|
||||
var incrementalParse: IncrementalParse = TypeScript.IncrementalParser.parse;
|
||||
var incrementalParse: IncrementalParse = TypeScript.IncrementalParser.parse;
|
||||
|
||||
class SourceFileObject extends NodeObject implements SourceFile {
|
||||
public filename: string;
|
||||
@@ -430,6 +430,9 @@ module ts {
|
||||
getSemanticDiagnostics(fileName: string): Diagnostic[];
|
||||
getCompilerOptionsDiagnostics(): Diagnostic[];
|
||||
|
||||
getSyntacticClassifications(fileName: string, span: TypeScript.TextSpan): ClassifiedSpan[];
|
||||
getSemanticClassifications(fileName: string, span: TypeScript.TextSpan): ClassifiedSpan[];
|
||||
|
||||
getCompletionsAtPosition(fileName: string, position: number, isMemberCompletion: boolean): CompletionInfo;
|
||||
getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails;
|
||||
|
||||
@@ -467,6 +470,32 @@ module ts {
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export class ClassificationTypeNames {
|
||||
public static comment = "comment";
|
||||
public static identifier = "identifier";
|
||||
public static keyword = "keyword";
|
||||
public static numericLiteral = "number";
|
||||
public static operator = "operator";
|
||||
public static stringLiteral = "string";
|
||||
public static whiteSpace = "whitespace";
|
||||
public static text = "text";
|
||||
|
||||
public static punctuation = "punctuation";
|
||||
|
||||
public static className = "class name";
|
||||
public static enumName = "enum name";
|
||||
public static interfaceName = "interface name";
|
||||
public static moduleName = "module name";
|
||||
public static typeParameterName = "type parameter name";
|
||||
}
|
||||
|
||||
export class ClassifiedSpan {
|
||||
constructor(public textSpan: TypeScript.TextSpan,
|
||||
public classificationType: string) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export class NavigationBarItem {
|
||||
constructor(public text: string,
|
||||
public kind: string,
|
||||
@@ -3214,6 +3243,198 @@ module ts {
|
||||
return new TypeScript.Services.NavigationBarItemGetter().getItems(syntaxTree.sourceUnit());
|
||||
}
|
||||
|
||||
function getSemanticClassifications(fileName: string, span: TypeScript.TextSpan): ClassifiedSpan[] {
|
||||
synchronizeHostData();
|
||||
fileName = TypeScript.switchToForwardSlashes(fileName);
|
||||
|
||||
var sourceFile = getSourceFile(fileName);
|
||||
|
||||
var result: ClassifiedSpan[] = [];
|
||||
processNode(sourceFile);
|
||||
|
||||
return result;
|
||||
|
||||
function classifySymbol(symbol: Symbol) {
|
||||
var flags = symbol.getFlags();
|
||||
|
||||
if (flags & SymbolFlags.Class) {
|
||||
return ClassificationTypeNames.className;
|
||||
}
|
||||
else if (flags & SymbolFlags.Enum) {
|
||||
return ClassificationTypeNames.enumName;
|
||||
}
|
||||
else if (flags & SymbolFlags.Interface) {
|
||||
return ClassificationTypeNames.interfaceName;
|
||||
}
|
||||
else if (flags & SymbolFlags.Module) {
|
||||
return ClassificationTypeNames.moduleName;
|
||||
}
|
||||
else if (flags & SymbolFlags.TypeParameter) {
|
||||
return ClassificationTypeNames.typeParameterName;
|
||||
}
|
||||
}
|
||||
|
||||
function processNode(node: Node) {
|
||||
// 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 type = classifySymbol(symbol);
|
||||
if (type) {
|
||||
result.push(new ClassifiedSpan(
|
||||
new TypeScript.TextSpan(node.getStart(), node.getWidth()),
|
||||
type));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
forEachChild(node, processNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getSyntacticClassifications(fileName: string, span: TypeScript.TextSpan): ClassifiedSpan[] {
|
||||
// doesn't use compiler - no need to synchronize with host
|
||||
fileName = TypeScript.switchToForwardSlashes(fileName);
|
||||
var sourceFile = getCurrentSourceFile(fileName);
|
||||
|
||||
var result: ClassifiedSpan[] = [];
|
||||
processElement(sourceFile.getSourceUnit());
|
||||
|
||||
return result;
|
||||
|
||||
function classifyTrivia(trivia: TypeScript.ISyntaxTrivia) {
|
||||
if (trivia.isComment() && span.intersectsWith(trivia.fullStart(), trivia.fullWidth())) {
|
||||
result.push(new ClassifiedSpan(
|
||||
new TypeScript.TextSpan(trivia.fullStart(), trivia.fullWidth()),
|
||||
ClassificationTypeNames.comment));
|
||||
}
|
||||
}
|
||||
|
||||
function classifyTriviaList(trivia: TypeScript.ISyntaxTriviaList) {
|
||||
for (var i = 0, n = trivia.count(); i < n; i++) {
|
||||
classifyTrivia(trivia.syntaxTriviaAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
function classifyToken(token: TypeScript.ISyntaxToken) {
|
||||
if (token.hasLeadingComment()) {
|
||||
classifyTriviaList(token.leadingTrivia());
|
||||
}
|
||||
|
||||
if (TypeScript.width(token) > 0) {
|
||||
var type = classifyTokenType(token);
|
||||
if (type) {
|
||||
result.push(new ClassifiedSpan(
|
||||
new TypeScript.TextSpan(TypeScript.start(token), TypeScript.width(token)),
|
||||
type));
|
||||
}
|
||||
}
|
||||
|
||||
if (token.hasTrailingComment()) {
|
||||
classifyTriviaList(token.trailingTrivia());
|
||||
}
|
||||
}
|
||||
|
||||
function classifyTokenType(token: TypeScript.ISyntaxToken): string {
|
||||
var tokenKind = token.kind();
|
||||
if (TypeScript.SyntaxFacts.isAnyKeyword(token.kind())) {
|
||||
return ClassificationTypeNames.keyword;
|
||||
}
|
||||
|
||||
// Special case < and > If they appear in a generic context they are punctation,
|
||||
// not operators.
|
||||
if (tokenKind === TypeScript.SyntaxKind.LessThanToken || tokenKind === TypeScript.SyntaxKind.GreaterThanToken) {
|
||||
var tokenParentKind = token.parent.kind();
|
||||
if (tokenParentKind === TypeScript.SyntaxKind.TypeArgumentList ||
|
||||
tokenParentKind === TypeScript.SyntaxKind.TypeParameterList) {
|
||||
|
||||
return ClassificationTypeNames.punctuation;
|
||||
}
|
||||
}
|
||||
|
||||
if (TypeScript.SyntaxFacts.isBinaryExpressionOperatorToken(tokenKind) ||
|
||||
TypeScript.SyntaxFacts.isPrefixUnaryExpressionOperatorToken(tokenKind)) {
|
||||
return ClassificationTypeNames.operator;
|
||||
}
|
||||
else if (TypeScript.SyntaxFacts.isAnyPunctuation(tokenKind)) {
|
||||
return ClassificationTypeNames.punctuation;
|
||||
}
|
||||
else if (tokenKind === TypeScript.SyntaxKind.NumericLiteral) {
|
||||
return ClassificationTypeNames.numericLiteral;
|
||||
}
|
||||
else if (tokenKind === TypeScript.SyntaxKind.StringLiteral) {
|
||||
return ClassificationTypeNames.stringLiteral;
|
||||
}
|
||||
else if (tokenKind === TypeScript.SyntaxKind.RegularExpressionLiteral) {
|
||||
// TODO: we shoudl get another classification type for these literals.
|
||||
return ClassificationTypeNames.stringLiteral;
|
||||
}
|
||||
else if (tokenKind === TypeScript.SyntaxKind.IdentifierName) {
|
||||
var current: TypeScript.ISyntaxNodeOrToken = token;
|
||||
var parent = token.parent;
|
||||
while (parent.kind() === TypeScript.SyntaxKind.QualifiedName) {
|
||||
current = parent;
|
||||
parent = parent.parent;
|
||||
}
|
||||
|
||||
switch (parent.kind()) {
|
||||
case TypeScript.SyntaxKind.SimplePropertyAssignment:
|
||||
if ((<TypeScript.SimplePropertyAssignmentSyntax>parent).propertyName === token) {
|
||||
return ClassificationTypeNames.identifier;
|
||||
}
|
||||
return;
|
||||
case TypeScript.SyntaxKind.ClassDeclaration:
|
||||
if ((<TypeScript.ClassDeclarationSyntax>parent).identifier === token) {
|
||||
return ClassificationTypeNames.className;
|
||||
}
|
||||
return;
|
||||
case TypeScript.SyntaxKind.TypeParameter:
|
||||
if ((<TypeScript.TypeParameterSyntax>parent).identifier === token) {
|
||||
return ClassificationTypeNames.typeParameterName;
|
||||
}
|
||||
return;
|
||||
case TypeScript.SyntaxKind.InterfaceDeclaration:
|
||||
if ((<TypeScript.InterfaceDeclarationSyntax>parent).identifier === token) {
|
||||
return ClassificationTypeNames.interfaceName;
|
||||
}
|
||||
return;
|
||||
case TypeScript.SyntaxKind.EnumDeclaration:
|
||||
if ((<TypeScript.EnumDeclarationSyntax>parent).identifier === token) {
|
||||
return ClassificationTypeNames.enumName;
|
||||
}
|
||||
return;
|
||||
case TypeScript.SyntaxKind.ModuleDeclaration:
|
||||
if ((<TypeScript.ModuleDeclarationSyntax>parent).name === current) {
|
||||
return ClassificationTypeNames.moduleName;
|
||||
}
|
||||
return;
|
||||
default:
|
||||
return ClassificationTypeNames.text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function processElement(element: TypeScript.ISyntaxElement) {
|
||||
// Ignore nodes that don't intersect the original span to classify.
|
||||
if (!TypeScript.isShared(element) && span.intersectsWith(TypeScript.fullStart(element), TypeScript.fullWidth(element))) {
|
||||
for (var i = 0, n = TypeScript.childCount(element); i < n; i++) {
|
||||
var child = TypeScript.childAt(element, i);
|
||||
if (child) {
|
||||
if (TypeScript.isToken(child)) {
|
||||
classifyToken(<TypeScript.ISyntaxToken>child);
|
||||
}
|
||||
else {
|
||||
// Recurse into our child nodes.
|
||||
processElement(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getOutliningSpans(filename: string): OutliningSpan[] {
|
||||
// doesn't use compiler - no need to synchronize with host
|
||||
filename = TypeScript.switchToForwardSlashes(filename);
|
||||
@@ -3461,6 +3682,8 @@ module ts {
|
||||
getSyntacticDiagnostics: getSyntacticDiagnostics,
|
||||
getSemanticDiagnostics: getSemanticDiagnostics,
|
||||
getCompilerOptionsDiagnostics: getCompilerOptionsDiagnostics,
|
||||
getSyntacticClassifications: getSyntacticClassifications,
|
||||
getSemanticClassifications: getSemanticClassifications,
|
||||
getCompletionsAtPosition: getCompletionsAtPosition,
|
||||
getCompletionEntryDetails: getCompletionEntryDetails,
|
||||
getTypeAtPosition: getTypeAtPosition,
|
||||
|
||||
@@ -80,6 +80,8 @@ module ts {
|
||||
getSemanticDiagnostics(fileName: string): string;
|
||||
getCompilerOptionsDiagnostics(): string;
|
||||
|
||||
getSyntacticClassifications(fileName: string, start: number, length: number): string;
|
||||
|
||||
getCompletionsAtPosition(fileName: string, position: number, isMemberCompletion: boolean): string;
|
||||
getCompletionEntryDetails(fileName: string, position: number, entryName: string): string;
|
||||
|
||||
@@ -477,6 +479,24 @@ module ts {
|
||||
};
|
||||
}
|
||||
|
||||
public getSyntacticClassifications(fileName: string, start: number, length: number): string {
|
||||
return this.forwardJSONCall(
|
||||
"getSyntacticClassifications('" + fileName + "', " + start + ", " + length + ")",
|
||||
() => {
|
||||
var classifications = this.languageService.getSyntacticClassifications(fileName, new TypeScript.TextSpan(start, length));
|
||||
return classifications;
|
||||
});
|
||||
}
|
||||
|
||||
public getSemanticClassifications(fileName: string, start: number, length: number): string {
|
||||
return this.forwardJSONCall(
|
||||
"getSemanticClassifications('" + fileName + "', " + start + ", " + length + ")",
|
||||
() => {
|
||||
var classifications = this.languageService.getSemanticClassifications(fileName, new TypeScript.TextSpan(start, length));
|
||||
return classifications;
|
||||
});
|
||||
}
|
||||
|
||||
public getSyntacticDiagnostics(fileName: string): string {
|
||||
return this.forwardJSONCall(
|
||||
"getSyntacticDiagnostics('" + fileName + "')",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
///<reference path='references.ts' />
|
||||
|
||||
module TypeScript {
|
||||
|
||||
export interface ISpan {
|
||||
start(): number;
|
||||
end(): number;
|
||||
|
||||
Reference in New Issue
Block a user