mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Import the semantic highlighter from typescript-vscode-sh-plugin (#39119)
* Initial import of the vscode semantic highlight code * Adds the ability to test modern semantic classification via strings instead of numbers * Adds existing tests * Port over the semantic classification tests * Update baselines * Update src/harness/fourslashImpl.ts Co-authored-by: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> * Handle feedback from #39119 * Consistent formatting in the 2020 classifier * Update baselines * Apply suggestions from code review Co-authored-by: Daniel Rosenwasser <DanielRosenwasser@users.noreply.github.com> * Update src/harness/fourslashImpl.ts Co-authored-by: Daniel Rosenwasser <DanielRosenwasser@users.noreply.github.com> * Reafactor after comments * Use 2020 everywhere * Handle feedback * WIP - don't provide a breaking change * Fix all build errors * Update baselines * Update src/services/classifier2020.ts Co-authored-by: Sheetal Nandi <shkamat@microsoft.com> * Addresses Ron's feedback Co-authored-by: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Co-authored-by: Daniel Rosenwasser <DanielRosenwasser@users.noreply.github.com> Co-authored-by: Sheetal Nandi <shkamat@microsoft.com>
This commit is contained in:
co-authored by
Nathan Shively-Sanders
Daniel Rosenwasser
Sheetal Nandi
parent
a36f17c1f8
commit
db5368dc1d
@@ -747,7 +747,7 @@ namespace ts.server {
|
||||
return notImplemented();
|
||||
}
|
||||
|
||||
getEncodedSemanticClassifications(_fileName: string, _span: TextSpan): Classifications {
|
||||
getEncodedSemanticClassifications(_fileName: string, _span: TextSpan, _format?: SemanticClassificationFormat): Classifications {
|
||||
return notImplemented();
|
||||
}
|
||||
|
||||
|
||||
@@ -2492,7 +2492,49 @@ namespace FourSlash {
|
||||
Harness.IO.log(this.spanInfoToString(this.getNameOrDottedNameSpan(pos)!, "**"));
|
||||
}
|
||||
|
||||
private verifyClassifications(expected: { classificationType: string; text: string; textSpan?: TextSpan }[], actual: ts.ClassifiedSpan[], sourceFileText: string) {
|
||||
private classificationToIdentifier(classification: number){
|
||||
|
||||
const tokenTypes: string[] = [];
|
||||
tokenTypes[ts.classifier.v2020.TokenType.class] = "class";
|
||||
tokenTypes[ts.classifier.v2020.TokenType.enum] = "enum";
|
||||
tokenTypes[ts.classifier.v2020.TokenType.interface] = "interface";
|
||||
tokenTypes[ts.classifier.v2020.TokenType.namespace] = "namespace";
|
||||
tokenTypes[ts.classifier.v2020.TokenType.typeParameter] = "typeParameter";
|
||||
tokenTypes[ts.classifier.v2020.TokenType.type] = "type";
|
||||
tokenTypes[ts.classifier.v2020.TokenType.parameter] = "parameter";
|
||||
tokenTypes[ts.classifier.v2020.TokenType.variable] = "variable";
|
||||
tokenTypes[ts.classifier.v2020.TokenType.enumMember] = "enumMember";
|
||||
tokenTypes[ts.classifier.v2020.TokenType.property] = "property";
|
||||
tokenTypes[ts.classifier.v2020.TokenType.function] = "function";
|
||||
tokenTypes[ts.classifier.v2020.TokenType.member] = "member";
|
||||
|
||||
const tokenModifiers: string[] = [];
|
||||
tokenModifiers[ts.classifier.v2020.TokenModifier.async] = "async";
|
||||
tokenModifiers[ts.classifier.v2020.TokenModifier.declaration] = "declaration";
|
||||
tokenModifiers[ts.classifier.v2020.TokenModifier.readonly] = "readonly";
|
||||
tokenModifiers[ts.classifier.v2020.TokenModifier.static] = "static";
|
||||
tokenModifiers[ts.classifier.v2020.TokenModifier.local] = "local";
|
||||
tokenModifiers[ts.classifier.v2020.TokenModifier.defaultLibrary] = "defaultLibrary";
|
||||
|
||||
|
||||
function getTokenTypeFromClassification(tsClassification: number): number | undefined {
|
||||
if (tsClassification > ts.classifier.v2020.TokenEncodingConsts.modifierMask) {
|
||||
return (tsClassification >> ts.classifier.v2020.TokenEncodingConsts.typeOffset) - 1;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getTokenModifierFromClassification(tsClassification: number) {
|
||||
return tsClassification & ts.classifier.v2020.TokenEncodingConsts.modifierMask;
|
||||
}
|
||||
|
||||
const typeIdx = getTokenTypeFromClassification(classification) || 0;
|
||||
const modSet = getTokenModifierFromClassification(classification);
|
||||
|
||||
return [tokenTypes[typeIdx], ...tokenModifiers.filter((_, i) => modSet & 1 << i)].join(".");
|
||||
}
|
||||
|
||||
private verifyClassifications(expected: { classificationType: string | number, text?: string; textSpan?: TextSpan }[], actual: (ts.ClassifiedSpan | ts.ClassifiedSpan2020)[] , sourceFileText: string) {
|
||||
if (actual.length !== expected.length) {
|
||||
this.raiseError("verifyClassifications failed - expected total classifications to be " + expected.length +
|
||||
", but was " + actual.length +
|
||||
@@ -2501,10 +2543,12 @@ namespace FourSlash {
|
||||
|
||||
ts.zipWith(expected, actual, (expectedClassification, actualClassification) => {
|
||||
const expectedType = expectedClassification.classificationType;
|
||||
if (expectedType !== actualClassification.classificationType) {
|
||||
const actualType = typeof actualClassification.classificationType === "number" ? this.classificationToIdentifier(actualClassification.classificationType) : actualClassification.classificationType;
|
||||
|
||||
if (expectedType !== actualType) {
|
||||
this.raiseError("verifyClassifications failed - expected classifications type to be " +
|
||||
expectedType + ", but was " +
|
||||
actualClassification.classificationType +
|
||||
actualType +
|
||||
jsonMismatchString());
|
||||
}
|
||||
|
||||
@@ -2555,10 +2599,30 @@ namespace FourSlash {
|
||||
}
|
||||
}
|
||||
|
||||
public verifySemanticClassifications(expected: { classificationType: string; text: string }[]) {
|
||||
public replaceWithSemanticClassifications(format: ts.SemanticClassificationFormat.TwentyTwenty) {
|
||||
const actual = this.languageService.getSemanticClassifications(this.activeFile.fileName,
|
||||
ts.createTextSpan(0, this.activeFile.content.length));
|
||||
ts.createTextSpan(0, this.activeFile.content.length), format);
|
||||
const replacement = [`const c2 = classification("2020");`,`verify.semanticClassificationsAre("2020",`];
|
||||
for (const a of actual) {
|
||||
const identifier = this.classificationToIdentifier(a.classificationType as number);
|
||||
const text = this.activeFile.content.slice(a.textSpan.start, a.textSpan.start + a.textSpan.length);
|
||||
replacement.push(` c2.semanticToken("${identifier}", "${text}"), `);
|
||||
};
|
||||
replacement.push(");");
|
||||
|
||||
throw new Error("You need to change the source code of fourslash test to use replaceWithSemanticClassifications");
|
||||
|
||||
// const fs = require("fs");
|
||||
// const testfilePath = this.originalInputFileName.slice(1);
|
||||
// const testfile = fs.readFileSync(testfilePath, "utf8");
|
||||
// const newfile = testfile.replace("verify.replaceWithSemanticClassifications(\"2020\")", replacement.join("\n"));
|
||||
// fs.writeFileSync(testfilePath, newfile);
|
||||
}
|
||||
|
||||
|
||||
public verifySemanticClassifications(format: ts.SemanticClassificationFormat, expected: { classificationType: string | number; text?: string }[]) {
|
||||
const actual = this.languageService.getSemanticClassifications(this.activeFile.fileName,
|
||||
ts.createTextSpan(0, this.activeFile.content.length), format);
|
||||
this.verifyClassifications(expected, actual, this.activeFile.content);
|
||||
}
|
||||
|
||||
@@ -3859,7 +3923,7 @@ namespace FourSlash {
|
||||
const cancellation = new FourSlashInterface.Cancellation(state);
|
||||
// eslint-disable-next-line no-eval
|
||||
const f = eval(wrappedCode);
|
||||
f(test, goTo, plugins, verify, edit, debug, format, cancellation, FourSlashInterface.Classification, FourSlashInterface.Completion, verifyOperationIsCancelled);
|
||||
f(test, goTo, plugins, verify, edit, debug, format, cancellation, FourSlashInterface.classification, FourSlashInterface.Completion, verifyOperationIsCancelled);
|
||||
}
|
||||
catch (err) {
|
||||
// ensure 'source-map-support' is triggered while we still have the handler attached by accessing `error.stack`.
|
||||
|
||||
@@ -524,8 +524,12 @@ namespace FourSlashInterface {
|
||||
/**
|
||||
* This method *requires* an ordered stream of classifications for a file, and spans are highly recommended.
|
||||
*/
|
||||
public semanticClassificationsAre(...classifications: Classification[]) {
|
||||
this.state.verifySemanticClassifications(classifications);
|
||||
public semanticClassificationsAre(format: ts.SemanticClassificationFormat, ...classifications: Classification[]) {
|
||||
this.state.verifySemanticClassifications(format, classifications);
|
||||
}
|
||||
|
||||
public replaceWithSemanticClassifications(format: ts.SemanticClassificationFormat.TwentyTwenty) {
|
||||
this.state.replaceWithSemanticClassifications(format);
|
||||
}
|
||||
|
||||
public renameInfoSucceeded(displayName?: string, fullDisplayName?: string, kind?: string, kindModifiers?: string, fileToRename?: string, expectedRange?: FourSlash.Range, options?: ts.RenameInfoOptions) {
|
||||
@@ -768,101 +772,127 @@ namespace FourSlashInterface {
|
||||
}
|
||||
}
|
||||
|
||||
interface Classification {
|
||||
interface OlderClassification {
|
||||
classificationType: ts.ClassificationTypeNames;
|
||||
text: string;
|
||||
textSpan?: FourSlash.TextSpan;
|
||||
}
|
||||
export namespace Classification {
|
||||
export function comment(text: string, position?: number): Classification {
|
||||
|
||||
// The VS Code LSP
|
||||
interface ModernClassification {
|
||||
classificationType: string;
|
||||
text?: string;
|
||||
textSpan?: FourSlash.TextSpan;
|
||||
}
|
||||
|
||||
type Classification = OlderClassification | ModernClassification;
|
||||
|
||||
export function classification(format: ts.SemanticClassificationFormat) {
|
||||
|
||||
function semanticToken(identifier: string, text: string, _position: number): Classification {
|
||||
return {
|
||||
classificationType: identifier,
|
||||
text
|
||||
};
|
||||
}
|
||||
|
||||
if (format === ts.SemanticClassificationFormat.TwentyTwenty) {
|
||||
return {
|
||||
semanticToken
|
||||
};
|
||||
}
|
||||
|
||||
// Defaults to the previous semantic classifier factory functions
|
||||
|
||||
function comment(text: string, position?: number): Classification {
|
||||
return getClassification(ts.ClassificationTypeNames.comment, text, position);
|
||||
}
|
||||
|
||||
export function identifier(text: string, position?: number): Classification {
|
||||
function identifier(text: string, position?: number): Classification {
|
||||
return getClassification(ts.ClassificationTypeNames.identifier, text, position);
|
||||
}
|
||||
|
||||
export function keyword(text: string, position?: number): Classification {
|
||||
function keyword(text: string, position?: number): Classification {
|
||||
return getClassification(ts.ClassificationTypeNames.keyword, text, position);
|
||||
}
|
||||
|
||||
export function numericLiteral(text: string, position?: number): Classification {
|
||||
function numericLiteral(text: string, position?: number): Classification {
|
||||
return getClassification(ts.ClassificationTypeNames.numericLiteral, text, position);
|
||||
}
|
||||
|
||||
export function operator(text: string, position?: number): Classification {
|
||||
function operator(text: string, position?: number): Classification {
|
||||
return getClassification(ts.ClassificationTypeNames.operator, text, position);
|
||||
}
|
||||
|
||||
export function stringLiteral(text: string, position?: number): Classification {
|
||||
function stringLiteral(text: string, position?: number): Classification {
|
||||
return getClassification(ts.ClassificationTypeNames.stringLiteral, text, position);
|
||||
}
|
||||
|
||||
export function whiteSpace(text: string, position?: number): Classification {
|
||||
function whiteSpace(text: string, position?: number): Classification {
|
||||
return getClassification(ts.ClassificationTypeNames.whiteSpace, text, position);
|
||||
}
|
||||
|
||||
export function text(text: string, position?: number): Classification {
|
||||
function text(text: string, position?: number): Classification {
|
||||
return getClassification(ts.ClassificationTypeNames.text, text, position);
|
||||
}
|
||||
|
||||
export function punctuation(text: string, position?: number): Classification {
|
||||
function punctuation(text: string, position?: number): Classification {
|
||||
return getClassification(ts.ClassificationTypeNames.punctuation, text, position);
|
||||
}
|
||||
|
||||
export function docCommentTagName(text: string, position?: number): Classification {
|
||||
function docCommentTagName(text: string, position?: number): Classification {
|
||||
return getClassification(ts.ClassificationTypeNames.docCommentTagName, text, position);
|
||||
}
|
||||
|
||||
export function className(text: string, position?: number): Classification {
|
||||
function className(text: string, position?: number): Classification {
|
||||
return getClassification(ts.ClassificationTypeNames.className, text, position);
|
||||
}
|
||||
|
||||
export function enumName(text: string, position?: number): Classification {
|
||||
function enumName(text: string, position?: number): Classification {
|
||||
return getClassification(ts.ClassificationTypeNames.enumName, text, position);
|
||||
}
|
||||
|
||||
export function interfaceName(text: string, position?: number): Classification {
|
||||
function interfaceName(text: string, position?: number): Classification {
|
||||
return getClassification(ts.ClassificationTypeNames.interfaceName, text, position);
|
||||
}
|
||||
|
||||
export function moduleName(text: string, position?: number): Classification {
|
||||
function moduleName(text: string, position?: number): Classification {
|
||||
return getClassification(ts.ClassificationTypeNames.moduleName, text, position);
|
||||
}
|
||||
|
||||
export function typeParameterName(text: string, position?: number): Classification {
|
||||
function typeParameterName(text: string, position?: number): Classification {
|
||||
return getClassification(ts.ClassificationTypeNames.typeParameterName, text, position);
|
||||
}
|
||||
|
||||
export function parameterName(text: string, position?: number): Classification {
|
||||
function parameterName(text: string, position?: number): Classification {
|
||||
return getClassification(ts.ClassificationTypeNames.parameterName, text, position);
|
||||
}
|
||||
|
||||
export function typeAliasName(text: string, position?: number): Classification {
|
||||
function typeAliasName(text: string, position?: number): Classification {
|
||||
return getClassification(ts.ClassificationTypeNames.typeAliasName, text, position);
|
||||
}
|
||||
|
||||
export function jsxOpenTagName(text: string, position?: number): Classification {
|
||||
function jsxOpenTagName(text: string, position?: number): Classification {
|
||||
return getClassification(ts.ClassificationTypeNames.jsxOpenTagName, text, position);
|
||||
}
|
||||
|
||||
export function jsxCloseTagName(text: string, position?: number): Classification {
|
||||
function jsxCloseTagName(text: string, position?: number): Classification {
|
||||
return getClassification(ts.ClassificationTypeNames.jsxCloseTagName, text, position);
|
||||
}
|
||||
|
||||
export function jsxSelfClosingTagName(text: string, position?: number): Classification {
|
||||
function jsxSelfClosingTagName(text: string, position?: number): Classification {
|
||||
return getClassification(ts.ClassificationTypeNames.jsxSelfClosingTagName, text, position);
|
||||
}
|
||||
|
||||
export function jsxAttribute(text: string, position?: number): Classification {
|
||||
function jsxAttribute(text: string, position?: number): Classification {
|
||||
return getClassification(ts.ClassificationTypeNames.jsxAttribute, text, position);
|
||||
}
|
||||
|
||||
export function jsxText(text: string, position?: number): Classification {
|
||||
function jsxText(text: string, position?: number): Classification {
|
||||
return getClassification(ts.ClassificationTypeNames.jsxText, text, position);
|
||||
}
|
||||
|
||||
export function jsxAttributeStringLiteralValue(text: string, position?: number): Classification {
|
||||
function jsxAttributeStringLiteralValue(text: string, position?: number): Classification {
|
||||
return getClassification(ts.ClassificationTypeNames.jsxAttributeStringLiteralValue, text, position);
|
||||
}
|
||||
|
||||
@@ -870,7 +900,35 @@ namespace FourSlashInterface {
|
||||
const textSpan = position === undefined ? undefined : { start: position, end: position + text.length };
|
||||
return { classificationType, text, textSpan };
|
||||
}
|
||||
|
||||
return {
|
||||
comment,
|
||||
identifier,
|
||||
keyword,
|
||||
numericLiteral,
|
||||
operator,
|
||||
stringLiteral,
|
||||
whiteSpace,
|
||||
text,
|
||||
punctuation,
|
||||
docCommentTagName,
|
||||
className,
|
||||
enumName,
|
||||
interfaceName,
|
||||
moduleName,
|
||||
typeParameterName,
|
||||
parameterName,
|
||||
typeAliasName,
|
||||
jsxOpenTagName,
|
||||
jsxCloseTagName,
|
||||
jsxSelfClosingTagName,
|
||||
jsxAttribute,
|
||||
jsxText,
|
||||
jsxAttributeStringLiteralValue,
|
||||
getClassification
|
||||
};
|
||||
}
|
||||
|
||||
export namespace Completion {
|
||||
export import SortText = ts.Completions.SortText;
|
||||
export import CompletionSource = ts.Completions.CompletionSource;
|
||||
|
||||
@@ -461,14 +461,15 @@ namespace Harness.LanguageService {
|
||||
getSyntacticClassifications(fileName: string, span: ts.TextSpan): ts.ClassifiedSpan[] {
|
||||
return unwrapJSONCallResult(this.shim.getSyntacticClassifications(fileName, span.start, span.length));
|
||||
}
|
||||
getSemanticClassifications(fileName: string, span: ts.TextSpan): ts.ClassifiedSpan[] {
|
||||
return unwrapJSONCallResult(this.shim.getSemanticClassifications(fileName, span.start, span.length));
|
||||
getSemanticClassifications(fileName: string, span: ts.TextSpan, format?: ts.SemanticClassificationFormat): ts.ClassifiedSpan[] {
|
||||
return unwrapJSONCallResult(this.shim.getSemanticClassifications(fileName, span.start, span.length, format));
|
||||
}
|
||||
getEncodedSyntacticClassifications(fileName: string, span: ts.TextSpan): ts.Classifications {
|
||||
return unwrapJSONCallResult(this.shim.getEncodedSyntacticClassifications(fileName, span.start, span.length));
|
||||
}
|
||||
getEncodedSemanticClassifications(fileName: string, span: ts.TextSpan): ts.Classifications {
|
||||
return unwrapJSONCallResult(this.shim.getEncodedSemanticClassifications(fileName, span.start, span.length));
|
||||
getEncodedSemanticClassifications(fileName: string, span: ts.TextSpan, format?: ts.SemanticClassificationFormat): ts.Classifications {
|
||||
const responseFormat = format || ts.SemanticClassificationFormat.Original;
|
||||
return unwrapJSONCallResult(this.shim.getEncodedSemanticClassifications(fileName, span.start, span.length, responseFormat));
|
||||
}
|
||||
getCompletionsAtPosition(fileName: string, position: number, preferences: ts.UserPreferences | undefined): ts.CompletionInfo {
|
||||
return unwrapJSONCallResult(this.shim.getCompletionsAtPosition(fileName, position, preferences));
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
/** @internal */
|
||||
namespace ts.classifier.v2020 {
|
||||
|
||||
export const enum TokenEncodingConsts {
|
||||
typeOffset = 8,
|
||||
modifierMask = (1 << typeOffset) - 1
|
||||
}
|
||||
|
||||
export const enum TokenType {
|
||||
class, enum, interface, namespace, typeParameter, type, parameter, variable, enumMember, property, function, member
|
||||
}
|
||||
|
||||
export const enum TokenModifier {
|
||||
declaration, static, async, readonly, defaultLibrary, local
|
||||
}
|
||||
|
||||
/** This is mainly used internally for testing */
|
||||
export function getSemanticClassifications(program: Program, cancellationToken: CancellationToken, sourceFile: SourceFile, span: TextSpan): ClassifiedSpan2020[] {
|
||||
const classifications = getEncodedSemanticClassifications(program, cancellationToken, sourceFile, span);
|
||||
|
||||
Debug.assert(classifications.spans.length % 3 === 0);
|
||||
const dense = classifications.spans;
|
||||
const result: ClassifiedSpan2020[] = [];
|
||||
for (let i = 0; i < dense.length; i += 3) {
|
||||
result.push({
|
||||
textSpan: createTextSpan(dense[i], dense[i + 1]),
|
||||
classificationType: dense[i + 2]
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function getEncodedSemanticClassifications(program: Program, cancellationToken: CancellationToken, sourceFile: SourceFile, span: TextSpan): Classifications {
|
||||
return {
|
||||
spans: getSemanticTokens(program, sourceFile, span, cancellationToken),
|
||||
endOfLineState: EndOfLineState.None
|
||||
};
|
||||
}
|
||||
|
||||
function getSemanticTokens(program: Program, sourceFile: SourceFile, span: TextSpan, cancellationToken: CancellationToken): number[] {
|
||||
const resultTokens: number[] = [];
|
||||
|
||||
const collector = (node: Node, typeIdx: number, modifierSet: number) => {
|
||||
resultTokens.push(node.getStart(sourceFile), node.getWidth(sourceFile), ((typeIdx + 1) << TokenEncodingConsts.typeOffset) + modifierSet);
|
||||
};
|
||||
|
||||
if (program && sourceFile) {
|
||||
collectTokens(program, sourceFile, span, collector, cancellationToken);
|
||||
}
|
||||
return resultTokens;
|
||||
}
|
||||
|
||||
function collectTokens(program: Program, sourceFile: SourceFile, span: TextSpan, collector: (node: Node, tokenType: number, tokenModifier: number) => void, cancellationToken: CancellationToken) {
|
||||
const typeChecker = program.getTypeChecker();
|
||||
|
||||
let inJSXElement = false;
|
||||
|
||||
function visit(node: Node) {
|
||||
switch(node.kind) {
|
||||
case SyntaxKind.ModuleDeclaration:
|
||||
case SyntaxKind.ClassDeclaration:
|
||||
case SyntaxKind.InterfaceDeclaration:
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
case SyntaxKind.ClassExpression:
|
||||
case SyntaxKind.FunctionExpression:
|
||||
case SyntaxKind.ArrowFunction:
|
||||
cancellationToken.throwIfCancellationRequested();
|
||||
}
|
||||
|
||||
if (!node || !textSpanIntersectsWith(span, node.pos, node.getFullWidth()) || node.getFullWidth() === 0) {
|
||||
return;
|
||||
}
|
||||
const prevInJSXElement = inJSXElement;
|
||||
if (isJsxElement(node) || isJsxSelfClosingElement(node)) {
|
||||
inJSXElement = true;
|
||||
}
|
||||
if (isJsxExpression(node)) {
|
||||
inJSXElement = false;
|
||||
}
|
||||
|
||||
if (isIdentifier(node) && !inJSXElement && !inImportClause(node)) {
|
||||
let symbol = typeChecker.getSymbolAtLocation(node);
|
||||
if (symbol) {
|
||||
if (symbol.flags & SymbolFlags.Alias) {
|
||||
symbol = typeChecker.getAliasedSymbol(symbol);
|
||||
}
|
||||
let typeIdx = classifySymbol(symbol, getMeaningFromLocation(node));
|
||||
if (typeIdx !== undefined) {
|
||||
let modifierSet = 0;
|
||||
if (node.parent) {
|
||||
const parentIsDeclaration = (isBindingElement(node.parent) || tokenFromDeclarationMapping.get(node.parent.kind) === typeIdx);
|
||||
if (parentIsDeclaration && (<NamedDeclaration>node.parent).name === node) {
|
||||
modifierSet = 1 << TokenModifier.declaration;
|
||||
}
|
||||
}
|
||||
|
||||
// property declaration in constructor
|
||||
if (typeIdx === TokenType.parameter && isRightSideOfQualifiedNameOrPropertyAccess(node)) {
|
||||
typeIdx = TokenType.property;
|
||||
}
|
||||
|
||||
typeIdx = reclassifyByType(typeChecker, node, typeIdx);
|
||||
|
||||
const decl = symbol.valueDeclaration;
|
||||
if (decl) {
|
||||
const modifiers = getCombinedModifierFlags(decl);
|
||||
const nodeFlags = getCombinedNodeFlags(decl);
|
||||
if (modifiers & ModifierFlags.Static) {
|
||||
modifierSet |= 1 << TokenModifier.static;
|
||||
}
|
||||
if (modifiers & ModifierFlags.Async) {
|
||||
modifierSet |= 1 << TokenModifier.async;
|
||||
}
|
||||
if (typeIdx !== TokenType.class && typeIdx !== TokenType.interface) {
|
||||
if ((modifiers & ModifierFlags.Readonly) || (nodeFlags & NodeFlags.Const) || (symbol.getFlags() & SymbolFlags.EnumMember)) {
|
||||
modifierSet |= 1 << TokenModifier.readonly;
|
||||
}
|
||||
}
|
||||
if ((typeIdx === TokenType.variable || typeIdx === TokenType.function) && isLocalDeclaration(decl, sourceFile)) {
|
||||
modifierSet |= 1 << TokenModifier.local;
|
||||
}
|
||||
if (program.isSourceFileDefaultLibrary(decl.getSourceFile())) {
|
||||
modifierSet |= 1 << TokenModifier.defaultLibrary;
|
||||
}
|
||||
}
|
||||
else if (symbol.declarations && symbol.declarations.some(d => program.isSourceFileDefaultLibrary(d.getSourceFile()))) {
|
||||
modifierSet |= 1 << TokenModifier.defaultLibrary;
|
||||
}
|
||||
|
||||
collector(node, typeIdx, modifierSet);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
forEachChild(node, visit);
|
||||
|
||||
inJSXElement = prevInJSXElement;
|
||||
}
|
||||
visit(sourceFile);
|
||||
}
|
||||
|
||||
function classifySymbol(symbol: Symbol, meaning: SemanticMeaning): TokenType | undefined {
|
||||
const flags = symbol.getFlags();
|
||||
if (flags & SymbolFlags.Class) {
|
||||
return TokenType.class;
|
||||
}
|
||||
else if (flags & SymbolFlags.Enum) {
|
||||
return TokenType.enum;
|
||||
}
|
||||
else if (flags & SymbolFlags.TypeAlias) {
|
||||
return TokenType.type;
|
||||
}
|
||||
else if (flags & SymbolFlags.Interface) {
|
||||
if (meaning & SemanticMeaning.Type) {
|
||||
return TokenType.interface;
|
||||
}
|
||||
}
|
||||
else if (flags & SymbolFlags.TypeParameter) {
|
||||
return TokenType.typeParameter;
|
||||
}
|
||||
let decl = symbol.valueDeclaration || symbol.declarations && symbol.declarations[0];
|
||||
if (decl && isBindingElement(decl)) {
|
||||
decl = getDeclarationForBindingElement(decl);
|
||||
}
|
||||
return decl && tokenFromDeclarationMapping.get(decl.kind);
|
||||
}
|
||||
|
||||
function reclassifyByType(typeChecker: TypeChecker, node: Node, typeIdx: TokenType): TokenType {
|
||||
// type based classifications
|
||||
if (typeIdx === TokenType.variable || typeIdx === TokenType.property || typeIdx === TokenType.parameter) {
|
||||
const type = typeChecker.getTypeAtLocation(node);
|
||||
if (type) {
|
||||
const test = (condition: (type: Type) => boolean) => {
|
||||
return condition(type) || type.isUnion() && type.types.some(condition);
|
||||
};
|
||||
if (typeIdx !== TokenType.parameter && test(t => t.getConstructSignatures().length > 0)) {
|
||||
return TokenType.class;
|
||||
}
|
||||
if (test(t => t.getCallSignatures().length > 0) && !test(t => t.getProperties().length > 0) || isExpressionInCallExpression(node)) {
|
||||
return typeIdx === TokenType.property ? TokenType.member : TokenType.function;
|
||||
}
|
||||
}
|
||||
}
|
||||
return typeIdx;
|
||||
}
|
||||
|
||||
function isLocalDeclaration(decl: Declaration, sourceFile: SourceFile): boolean {
|
||||
if (isBindingElement(decl)) {
|
||||
decl = getDeclarationForBindingElement(decl);
|
||||
}
|
||||
if (isVariableDeclaration(decl)) {
|
||||
return (!isSourceFile(decl.parent.parent.parent) || isCatchClause(decl.parent)) && decl.getSourceFile() === sourceFile;
|
||||
}
|
||||
else if (isFunctionDeclaration(decl)) {
|
||||
return !isSourceFile(decl.parent) && decl.getSourceFile() === sourceFile;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function getDeclarationForBindingElement(element: BindingElement): VariableDeclaration | ParameterDeclaration {
|
||||
while (true) {
|
||||
if (isBindingElement(element.parent.parent)) {
|
||||
element = element.parent.parent;
|
||||
}
|
||||
else {
|
||||
return element.parent.parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function inImportClause(node: Node): boolean {
|
||||
const parent = node.parent;
|
||||
return parent && (isImportClause(parent) || isImportSpecifier(parent) || isNamespaceImport(parent));
|
||||
}
|
||||
|
||||
function isExpressionInCallExpression(node: Node): boolean {
|
||||
while (isRightSideOfQualifiedNameOrPropertyAccess(node)) {
|
||||
node = node.parent;
|
||||
}
|
||||
return isCallExpression(node.parent) && node.parent.expression === node;
|
||||
}
|
||||
|
||||
function isRightSideOfQualifiedNameOrPropertyAccess(node: Node): boolean {
|
||||
return (isQualifiedName(node.parent) && node.parent.right === node) || (isPropertyAccessExpression(node.parent) && node.parent.name === node);
|
||||
}
|
||||
|
||||
const tokenFromDeclarationMapping = new Map<SyntaxKind, TokenType>([
|
||||
[SyntaxKind.VariableDeclaration, TokenType.variable],
|
||||
[SyntaxKind.Parameter, TokenType.parameter],
|
||||
[SyntaxKind.PropertyDeclaration, TokenType.property],
|
||||
[SyntaxKind.ModuleDeclaration, TokenType.namespace],
|
||||
[SyntaxKind.EnumDeclaration, TokenType.enum],
|
||||
[SyntaxKind.EnumMember, TokenType.enumMember],
|
||||
[SyntaxKind.ClassDeclaration, TokenType.class],
|
||||
[SyntaxKind.MethodDeclaration, TokenType.member],
|
||||
[SyntaxKind.FunctionDeclaration, TokenType.function],
|
||||
[SyntaxKind.FunctionExpression, TokenType.function],
|
||||
[SyntaxKind.MethodSignature, TokenType.member],
|
||||
[SyntaxKind.GetAccessor, TokenType.property],
|
||||
[SyntaxKind.SetAccessor, TokenType.property],
|
||||
[SyntaxKind.PropertySignature, TokenType.property],
|
||||
[SyntaxKind.InterfaceDeclaration, TokenType.interface],
|
||||
[SyntaxKind.TypeAliasDeclaration, TokenType.type],
|
||||
[SyntaxKind.TypeParameter, TokenType.typeParameter],
|
||||
[SyntaxKind.PropertyAssignment, TokenType.property],
|
||||
[SyntaxKind.ShorthandPropertyAssignment, TokenType.property]
|
||||
]);
|
||||
}
|
||||
@@ -1827,22 +1827,37 @@ namespace ts {
|
||||
return kind === ScriptKind.TS || kind === ScriptKind.TSX;
|
||||
}
|
||||
|
||||
function getSemanticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[] {
|
||||
function getSemanticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[];
|
||||
function getSemanticClassifications(fileName: string, span: TextSpan, format?: SemanticClassificationFormat): ClassifiedSpan[] | ClassifiedSpan2020[] {
|
||||
if (!isTsOrTsxFile(fileName)) {
|
||||
// do not run semantic classification on non-ts-or-tsx files
|
||||
return [];
|
||||
}
|
||||
synchronizeHostData();
|
||||
return ts.getSemanticClassifications(program.getTypeChecker(), cancellationToken, getValidSourceFile(fileName), program.getClassifiableNames(), span);
|
||||
|
||||
const responseFormat = format || SemanticClassificationFormat.Original;
|
||||
if (responseFormat === SemanticClassificationFormat.TwentyTwenty) {
|
||||
return classifier.v2020.getSemanticClassifications(program, cancellationToken, getValidSourceFile(fileName), span);
|
||||
}
|
||||
else {
|
||||
return ts.getSemanticClassifications(program.getTypeChecker(), cancellationToken, getValidSourceFile(fileName), program.getClassifiableNames(), span);
|
||||
}
|
||||
}
|
||||
|
||||
function getEncodedSemanticClassifications(fileName: string, span: TextSpan): Classifications {
|
||||
function getEncodedSemanticClassifications(fileName: string, span: TextSpan, format?: SemanticClassificationFormat): Classifications {
|
||||
if (!isTsOrTsxFile(fileName)) {
|
||||
// do not run semantic classification on non-ts-or-tsx files
|
||||
return { spans: [], endOfLineState: EndOfLineState.None };
|
||||
}
|
||||
synchronizeHostData();
|
||||
return ts.getEncodedSemanticClassifications(program.getTypeChecker(), cancellationToken, getValidSourceFile(fileName), program.getClassifiableNames(), span);
|
||||
|
||||
const responseFormat = format || SemanticClassificationFormat.Original;
|
||||
if (responseFormat === SemanticClassificationFormat.Original) {
|
||||
return ts.getEncodedSemanticClassifications(program.getTypeChecker(), cancellationToken, getValidSourceFile(fileName), program.getClassifiableNames(), span);
|
||||
}
|
||||
else {
|
||||
return classifier.v2020.getEncodedSemanticClassifications(program, cancellationToken, getValidSourceFile(fileName), span);
|
||||
}
|
||||
}
|
||||
|
||||
function getSyntacticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[] {
|
||||
|
||||
@@ -145,9 +145,9 @@ namespace ts {
|
||||
getCompilerOptionsDiagnostics(): string;
|
||||
|
||||
getSyntacticClassifications(fileName: string, start: number, length: number): string;
|
||||
getSemanticClassifications(fileName: string, start: number, length: number): string;
|
||||
getSemanticClassifications(fileName: string, start: number, length: number, format?: SemanticClassificationFormat): string;
|
||||
getEncodedSyntacticClassifications(fileName: string, start: number, length: number): string;
|
||||
getEncodedSemanticClassifications(fileName: string, start: number, length: number): string;
|
||||
getEncodedSemanticClassifications(fileName: string, start: number, length: number, format?: SemanticClassificationFormat): string;
|
||||
|
||||
getCompletionsAtPosition(fileName: string, position: number, preferences: UserPreferences | undefined): string;
|
||||
getCompletionEntryDetails(fileName: string, position: number, entryName: string, formatOptions: string/*Services.FormatCodeOptions*/ | undefined, source: string | undefined, preferences: UserPreferences | undefined): string;
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"types.ts",
|
||||
"utilities.ts",
|
||||
"classifier.ts",
|
||||
"classifier2020.ts",
|
||||
"stringCompletions.ts",
|
||||
"completions.ts",
|
||||
"documentHighlights.ts",
|
||||
|
||||
+24
-2
@@ -319,6 +319,11 @@ namespace ts {
|
||||
|
||||
export type WithMetadata<T> = T & { metadata?: unknown; };
|
||||
|
||||
export const enum SemanticClassificationFormat {
|
||||
Original = "original",
|
||||
TwentyTwenty = "2020"
|
||||
}
|
||||
|
||||
//
|
||||
// Public services of a language service instance associated
|
||||
// with a language service host instance
|
||||
@@ -382,13 +387,25 @@ namespace ts {
|
||||
|
||||
/** @deprecated Use getEncodedSyntacticClassifications instead. */
|
||||
getSyntacticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[];
|
||||
getSyntacticClassifications(fileName: string, span: TextSpan, format: SemanticClassificationFormat): ClassifiedSpan[] | ClassifiedSpan2020[];
|
||||
|
||||
/** @deprecated Use getEncodedSemanticClassifications instead. */
|
||||
getSemanticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[];
|
||||
getSemanticClassifications(fileName: string, span: TextSpan, format: SemanticClassificationFormat): ClassifiedSpan[] | ClassifiedSpan2020[];
|
||||
|
||||
// Encoded as triples of [start, length, ClassificationType].
|
||||
/** Encoded as triples of [start, length, ClassificationType]. */
|
||||
getEncodedSyntacticClassifications(fileName: string, span: TextSpan): Classifications;
|
||||
getEncodedSemanticClassifications(fileName: string, span: TextSpan): Classifications;
|
||||
|
||||
/**
|
||||
* Gets semantic highlights information for a particular file. Has two formats, an older
|
||||
* version used by VS and a format used by VS Code.
|
||||
*
|
||||
* @param fileName The path to the file
|
||||
* @param position A text span to return results within
|
||||
* @param format Which format to use, defaults to "original"
|
||||
* @returns a number array encoded as triples of [start, length, ClassificationType, ...].
|
||||
*/
|
||||
getEncodedSemanticClassifications(fileName: string, span: TextSpan, format?: SemanticClassificationFormat): Classifications;
|
||||
|
||||
/**
|
||||
* Gets completion entries at a particular position in a file.
|
||||
@@ -603,6 +620,11 @@ namespace ts {
|
||||
classificationType: ClassificationTypeNames;
|
||||
}
|
||||
|
||||
export interface ClassifiedSpan2020 {
|
||||
textSpan: TextSpan;
|
||||
classificationType: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigation bar interface designed for visual studio's dual-column layout.
|
||||
* This does not form a proper tree.
|
||||
|
||||
Reference in New Issue
Block a user