/*1*/> completion list at "1" will contain "div" with type any
const tagName = (
location.parent.parent).openingElement.tagName;
entries.push({
name: (tagName).text,
kind: undefined,
kindModifiers: undefined,
sortText: "0",
});
}
else {
return undefined;
}
}
getCompletionEntriesFromSymbols(symbols, entries);
}
// Add keywords if this is not a member completion list
if (!isMemberCompletion && !isJsDocTagName) {
addRange(entries, keywordCompletions);
}
return { isMemberCompletion, isNewIdentifierLocation, entries };
function getJavaScriptCompletionEntries(sourceFile: SourceFile, position: number, uniqueNames: Map): CompletionEntry[] {
const entries: CompletionEntry[] = [];
const target = program.getCompilerOptions().target;
const nameTable = getNameTable(sourceFile);
for (const name in nameTable) {
// Skip identifiers produced only from the current location
if (nameTable[name] === position) {
continue;
}
if (!uniqueNames[name]) {
uniqueNames[name] = name;
const displayName = getCompletionEntryDisplayName(name, target, /*performCharacterChecks*/ true);
if (displayName) {
const entry = {
name: displayName,
kind: ScriptElementKind.warning,
kindModifiers: "",
sortText: "1"
};
entries.push(entry);
}
}
}
return entries;
}
function getAllJsDocCompletionEntries(): CompletionEntry[] {
return jsDocCompletionEntries || (jsDocCompletionEntries = ts.map(jsDocTagNames, tagName => {
return {
name: tagName,
kind: ScriptElementKind.keyword,
kindModifiers: "",
sortText: "0",
};
}));
}
function createCompletionEntry(symbol: Symbol, location: Node): CompletionEntry {
// Try to get a valid display name for this symbol, if we could not find one, then ignore it.
// We would like to only show things that can be added after a dot, so for instance numeric properties can
// not be accessed with a dot (a.1 <- invalid)
const displayName = getCompletionEntryDisplayNameForSymbol(symbol, program.getCompilerOptions().target, /*performCharacterChecks*/ true, location);
if (!displayName) {
return undefined;
}
// TODO(drosen): Right now we just permit *all* semantic meanings when calling
// 'getSymbolKind' which is permissible given that it is backwards compatible; but
// really we should consider passing the meaning for the node so that we don't report
// that a suggestion for a value is an interface. We COULD also just do what
// 'getSymbolModifiers' does, which is to use the first declaration.
// Use a 'sortText' of 0' so that all symbol completion entries come before any other
// entries (like JavaScript identifier entries).
return {
name: displayName,
kind: getSymbolKind(symbol, location),
kindModifiers: getSymbolModifiers(symbol),
sortText: "0",
};
}
function getCompletionEntriesFromSymbols(symbols: Symbol[], entries: CompletionEntry[]): Map {
const start = new Date().getTime();
const uniqueNames: Map = {};
if (symbols) {
for (const symbol of symbols) {
const entry = createCompletionEntry(symbol, location);
if (entry) {
const id = escapeIdentifier(entry.name);
if (!lookUp(uniqueNames, id)) {
entries.push(entry);
uniqueNames[id] = id;
}
}
}
}
log("getCompletionsAtPosition: getCompletionEntriesFromSymbols: " + (new Date().getTime() - start));
return uniqueNames;
}
}
function getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails {
synchronizeHostData();
// Compute all the completion symbols again.
const completionData = getCompletionData(fileName, position);
if (completionData) {
const { symbols, location } = completionData;
// Find the symbol with the matching entry name.
const target = program.getCompilerOptions().target;
// We don't need to perform character checks here because we're only comparing the
// name against 'entryName' (which is known to be good), not building a new
// completion entry.
const symbol = forEach(symbols, s => getCompletionEntryDisplayNameForSymbol(s, target, /*performCharacterChecks*/ false, location) === entryName ? s : undefined);
if (symbol) {
const { displayParts, documentation, symbolKind } = getSymbolDisplayPartsDocumentationAndSymbolKind(symbol, getValidSourceFile(fileName), location, location, SemanticMeaning.All);
return {
name: entryName,
kindModifiers: getSymbolModifiers(symbol),
kind: symbolKind,
displayParts,
documentation
};
}
}
// Didn't find a symbol with this name. See if we can find a keyword instead.
const keywordCompletion = forEach(keywordCompletions, c => c.name === entryName);
if (keywordCompletion) {
return {
name: entryName,
kind: ScriptElementKind.keyword,
kindModifiers: ScriptElementKindModifier.none,
displayParts: [displayPart(entryName, SymbolDisplayPartKind.keyword)],
documentation: undefined
};
}
return undefined;
}
// TODO(drosen): use contextual SemanticMeaning.
function getSymbolKind(symbol: Symbol, location: Node): string {
const flags = symbol.getFlags();
if (flags & SymbolFlags.Class) return getDeclarationOfKind(symbol, SyntaxKind.ClassExpression) ?
ScriptElementKind.localClassElement : ScriptElementKind.classElement;
if (flags & SymbolFlags.Enum) return ScriptElementKind.enumElement;
if (flags & SymbolFlags.TypeAlias) return ScriptElementKind.typeElement;
if (flags & SymbolFlags.Interface) return ScriptElementKind.interfaceElement;
if (flags & SymbolFlags.TypeParameter) return ScriptElementKind.typeParameterElement;
const result = getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(symbol, flags, location);
if (result === ScriptElementKind.unknown) {
if (flags & SymbolFlags.TypeParameter) return ScriptElementKind.typeParameterElement;
if (flags & SymbolFlags.EnumMember) return ScriptElementKind.variableElement;
if (flags & SymbolFlags.Alias) return ScriptElementKind.alias;
if (flags & SymbolFlags.Module) return ScriptElementKind.moduleElement;
}
return result;
}
function getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(symbol: Symbol, flags: SymbolFlags, location: Node) {
const typeChecker = program.getTypeChecker();
if (typeChecker.isUndefinedSymbol(symbol)) {
return ScriptElementKind.variableElement;
}
if (typeChecker.isArgumentsSymbol(symbol)) {
return ScriptElementKind.localVariableElement;
}
if (location.kind === SyntaxKind.ThisKeyword && isExpression(location)) {
return ScriptElementKind.parameterElement;
}
if (flags & SymbolFlags.Variable) {
if (isFirstDeclarationOfSymbolParameter(symbol)) {
return ScriptElementKind.parameterElement;
}
else if (symbol.valueDeclaration && isConst(symbol.valueDeclaration)) {
return ScriptElementKind.constElement;
}
else if (forEach(symbol.declarations, isLet)) {
return ScriptElementKind.letElement;
}
return isLocalVariableOrFunction(symbol) ? ScriptElementKind.localVariableElement : ScriptElementKind.variableElement;
}
if (flags & SymbolFlags.Function) return isLocalVariableOrFunction(symbol) ? ScriptElementKind.localFunctionElement : ScriptElementKind.functionElement;
if (flags & SymbolFlags.GetAccessor) return ScriptElementKind.memberGetAccessorElement;
if (flags & SymbolFlags.SetAccessor) return ScriptElementKind.memberSetAccessorElement;
if (flags & SymbolFlags.Method) return ScriptElementKind.memberFunctionElement;
if (flags & SymbolFlags.Constructor) return ScriptElementKind.constructorImplementationElement;
if (flags & SymbolFlags.Property) {
if (flags & SymbolFlags.SyntheticProperty) {
// If union property is result of union of non method (property/accessors/variables), it is labeled as property
const unionPropertyKind = forEach(typeChecker.getRootSymbols(symbol), rootSymbol => {
const rootSymbolFlags = rootSymbol.getFlags();
if (rootSymbolFlags & (SymbolFlags.PropertyOrAccessor | SymbolFlags.Variable)) {
return ScriptElementKind.memberVariableElement;
}
Debug.assert(!!(rootSymbolFlags & SymbolFlags.Method));
});
if (!unionPropertyKind) {
// If this was union of all methods,
// make sure it has call signatures before we can label it as method
const typeOfUnionProperty = typeChecker.getTypeOfSymbolAtLocation(symbol, location);
if (typeOfUnionProperty.getCallSignatures().length) {
return ScriptElementKind.memberFunctionElement;
}
return ScriptElementKind.memberVariableElement;
}
return unionPropertyKind;
}
return ScriptElementKind.memberVariableElement;
}
return ScriptElementKind.unknown;
}
function getSymbolModifiers(symbol: Symbol): string {
return symbol && symbol.declarations && symbol.declarations.length > 0
? getNodeModifiers(symbol.declarations[0])
: ScriptElementKindModifier.none;
}
// TODO(drosen): Currently completion entry details passes the SemanticMeaning.All instead of using semanticMeaning of location
function getSymbolDisplayPartsDocumentationAndSymbolKind(symbol: Symbol, sourceFile: SourceFile, enclosingDeclaration: Node,
location: Node, semanticMeaning = getMeaningFromLocation(location)) {
const typeChecker = program.getTypeChecker();
const displayParts: SymbolDisplayPart[] = [];
let documentation: SymbolDisplayPart[];
const symbolFlags = symbol.flags;
let symbolKind = getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(symbol, symbolFlags, location);
let hasAddedSymbolInfo: boolean;
const isThisExpression: boolean = location.kind === SyntaxKind.ThisKeyword && isExpression(location);
let type: Type;
// Class at constructor site need to be shown as constructor apart from property,method, vars
if (symbolKind !== ScriptElementKind.unknown || symbolFlags & SymbolFlags.Class || symbolFlags & SymbolFlags.Alias) {
// If it is accessor they are allowed only if location is at name of the accessor
if (symbolKind === ScriptElementKind.memberGetAccessorElement || symbolKind === ScriptElementKind.memberSetAccessorElement) {
symbolKind = ScriptElementKind.memberVariableElement;
}
let signature: Signature;
type = isThisExpression ? typeChecker.getTypeAtLocation(location) : typeChecker.getTypeOfSymbolAtLocation(symbol, location);
if (type) {
if (location.parent && location.parent.kind === SyntaxKind.PropertyAccessExpression) {
const right = (location.parent).name;
// Either the location is on the right of a property access, or on the left and the right is missing
if (right === location || (right && right.getFullWidth() === 0)) {
location = location.parent;
}
}
// try get the call/construct signature from the type if it matches
let callExpression: CallExpression;
if (location.kind === SyntaxKind.CallExpression || location.kind === SyntaxKind.NewExpression) {
callExpression = location;
}
else if (isCallExpressionTarget(location) || isNewExpressionTarget(location)) {
callExpression = location.parent;
}
if (callExpression) {
const candidateSignatures: Signature[] = [];
signature = typeChecker.getResolvedSignature(callExpression, candidateSignatures);
if (!signature && candidateSignatures.length) {
// Use the first candidate:
signature = candidateSignatures[0];
}
const useConstructSignatures = callExpression.kind === SyntaxKind.NewExpression || callExpression.expression.kind === SyntaxKind.SuperKeyword;
const allSignatures = useConstructSignatures ? type.getConstructSignatures() : type.getCallSignatures();
if (!contains(allSignatures, signature.target) && !contains(allSignatures, signature)) {
// Get the first signature if there is one -- allSignatures may contain
// either the original signature or its target, so check for either
signature = allSignatures.length ? allSignatures[0] : undefined;
}
if (signature) {
if (useConstructSignatures && (symbolFlags & SymbolFlags.Class)) {
// Constructor
symbolKind = ScriptElementKind.constructorImplementationElement;
addPrefixForAnyFunctionOrVar(type.symbol, symbolKind);
}
else if (symbolFlags & SymbolFlags.Alias) {
symbolKind = ScriptElementKind.alias;
pushTypePart(symbolKind);
displayParts.push(spacePart());
if (useConstructSignatures) {
displayParts.push(keywordPart(SyntaxKind.NewKeyword));
displayParts.push(spacePart());
}
addFullSymbolName(symbol);
}
else {
addPrefixForAnyFunctionOrVar(symbol, symbolKind);
}
switch (symbolKind) {
case ScriptElementKind.memberVariableElement:
case ScriptElementKind.variableElement:
case ScriptElementKind.constElement:
case ScriptElementKind.letElement:
case ScriptElementKind.parameterElement:
case ScriptElementKind.localVariableElement:
// If it is call or construct signature of lambda's write type name
displayParts.push(punctuationPart(SyntaxKind.ColonToken));
displayParts.push(spacePart());
if (useConstructSignatures) {
displayParts.push(keywordPart(SyntaxKind.NewKeyword));
displayParts.push(spacePart());
}
if (!(type.flags & TypeFlags.Anonymous) && type.symbol) {
addRange(displayParts, symbolToDisplayParts(typeChecker, type.symbol, enclosingDeclaration, /*meaning*/ undefined, SymbolFormatFlags.WriteTypeParametersOrArguments));
}
addSignatureDisplayParts(signature, allSignatures, TypeFormatFlags.WriteArrowStyleSignature);
break;
default:
// Just signature
addSignatureDisplayParts(signature, allSignatures);
}
hasAddedSymbolInfo = true;
}
}
else if ((isNameOfFunctionDeclaration(location) && !(symbol.flags & SymbolFlags.Accessor)) || // name of function declaration
(location.kind === SyntaxKind.ConstructorKeyword && location.parent.kind === SyntaxKind.Constructor)) { // At constructor keyword of constructor declaration
// get the signature from the declaration and write it
const functionDeclaration = location.parent;
const allSignatures = functionDeclaration.kind === SyntaxKind.Constructor ? type.getNonNullableType().getConstructSignatures() : type.getNonNullableType().getCallSignatures();
if (!typeChecker.isImplementationOfOverload(functionDeclaration)) {
signature = typeChecker.getSignatureFromDeclaration(functionDeclaration);
}
else {
signature = allSignatures[0];
}
if (functionDeclaration.kind === SyntaxKind.Constructor) {
// show (constructor) Type(...) signature
symbolKind = ScriptElementKind.constructorImplementationElement;
addPrefixForAnyFunctionOrVar(type.symbol, symbolKind);
}
else {
// (function/method) symbol(..signature)
addPrefixForAnyFunctionOrVar(functionDeclaration.kind === SyntaxKind.CallSignature &&
!(type.symbol.flags & SymbolFlags.TypeLiteral || type.symbol.flags & SymbolFlags.ObjectLiteral) ? type.symbol : symbol, symbolKind);
}
addSignatureDisplayParts(signature, allSignatures);
hasAddedSymbolInfo = true;
}
}
}
if (symbolFlags & SymbolFlags.Class && !hasAddedSymbolInfo && !isThisExpression) {
if (getDeclarationOfKind(symbol, SyntaxKind.ClassExpression)) {
// Special case for class expressions because we would like to indicate that
// the class name is local to the class body (similar to function expression)
// (local class) class
pushTypePart(ScriptElementKind.localClassElement);
}
else {
// Class declaration has name which is not local.
displayParts.push(keywordPart(SyntaxKind.ClassKeyword));
}
displayParts.push(spacePart());
addFullSymbolName(symbol);
writeTypeParametersOfSymbol(symbol, sourceFile);
}
if ((symbolFlags & SymbolFlags.Interface) && (semanticMeaning & SemanticMeaning.Type)) {
addNewLineIfDisplayPartsExist();
displayParts.push(keywordPart(SyntaxKind.InterfaceKeyword));
displayParts.push(spacePart());
addFullSymbolName(symbol);
writeTypeParametersOfSymbol(symbol, sourceFile);
}
if (symbolFlags & SymbolFlags.TypeAlias) {
addNewLineIfDisplayPartsExist();
displayParts.push(keywordPart(SyntaxKind.TypeKeyword));
displayParts.push(spacePart());
addFullSymbolName(symbol);
writeTypeParametersOfSymbol(symbol, sourceFile);
displayParts.push(spacePart());
displayParts.push(operatorPart(SyntaxKind.EqualsToken));
displayParts.push(spacePart());
addRange(displayParts, typeToDisplayParts(typeChecker, typeChecker.getDeclaredTypeOfSymbol(symbol), enclosingDeclaration));
}
if (symbolFlags & SymbolFlags.Enum) {
addNewLineIfDisplayPartsExist();
if (forEach(symbol.declarations, isConstEnumDeclaration)) {
displayParts.push(keywordPart(SyntaxKind.ConstKeyword));
displayParts.push(spacePart());
}
displayParts.push(keywordPart(SyntaxKind.EnumKeyword));
displayParts.push(spacePart());
addFullSymbolName(symbol);
}
if (symbolFlags & SymbolFlags.Module) {
addNewLineIfDisplayPartsExist();
const declaration = getDeclarationOfKind(symbol, SyntaxKind.ModuleDeclaration);
const isNamespace = declaration && declaration.name && declaration.name.kind === SyntaxKind.Identifier;
displayParts.push(keywordPart(isNamespace ? SyntaxKind.NamespaceKeyword : SyntaxKind.ModuleKeyword));
displayParts.push(spacePart());
addFullSymbolName(symbol);
}
if ((symbolFlags & SymbolFlags.TypeParameter) && (semanticMeaning & SemanticMeaning.Type)) {
addNewLineIfDisplayPartsExist();
displayParts.push(punctuationPart(SyntaxKind.OpenParenToken));
displayParts.push(textPart("type parameter"));
displayParts.push(punctuationPart(SyntaxKind.CloseParenToken));
displayParts.push(spacePart());
addFullSymbolName(symbol);
displayParts.push(spacePart());
displayParts.push(keywordPart(SyntaxKind.InKeyword));
displayParts.push(spacePart());
if (symbol.parent) {
// Class/Interface type parameter
addFullSymbolName(symbol.parent, enclosingDeclaration);
writeTypeParametersOfSymbol(symbol.parent, enclosingDeclaration);
}
else {
// Method/function type parameter
let declaration = getDeclarationOfKind(symbol, SyntaxKind.TypeParameter);
Debug.assert(declaration !== undefined);
declaration = declaration.parent;
if (declaration) {
if (isFunctionLikeKind(declaration.kind)) {
const signature = typeChecker.getSignatureFromDeclaration(declaration);
if (declaration.kind === SyntaxKind.ConstructSignature) {
displayParts.push(keywordPart(SyntaxKind.NewKeyword));
displayParts.push(spacePart());
}
else if (declaration.kind !== SyntaxKind.CallSignature && (declaration).name) {
addFullSymbolName(declaration.symbol);
}
addRange(displayParts, signatureToDisplayParts(typeChecker, signature, sourceFile, TypeFormatFlags.WriteTypeArgumentsOfSignature));
}
else {
// Type alias type parameter
// For example
// type list = T[]; // Both T will go through same code path
displayParts.push(keywordPart(SyntaxKind.TypeKeyword));
displayParts.push(spacePart());
addFullSymbolName(declaration.symbol);
writeTypeParametersOfSymbol(declaration.symbol, sourceFile);
}
}
}
}
if (symbolFlags & SymbolFlags.EnumMember) {
addPrefixForAnyFunctionOrVar(symbol, "enum member");
const declaration = symbol.declarations[0];
if (declaration.kind === SyntaxKind.EnumMember) {
const constantValue = typeChecker.getConstantValue(declaration);
if (constantValue !== undefined) {
displayParts.push(spacePart());
displayParts.push(operatorPart(SyntaxKind.EqualsToken));
displayParts.push(spacePart());
displayParts.push(displayPart(constantValue.toString(), SymbolDisplayPartKind.numericLiteral));
}
}
}
if (symbolFlags & SymbolFlags.Alias) {
addNewLineIfDisplayPartsExist();
displayParts.push(keywordPart(SyntaxKind.ImportKeyword));
displayParts.push(spacePart());
addFullSymbolName(symbol);
ts.forEach(symbol.declarations, declaration => {
if (declaration.kind === SyntaxKind.ImportEqualsDeclaration) {
const importEqualsDeclaration = declaration;
if (isExternalModuleImportEqualsDeclaration(importEqualsDeclaration)) {
displayParts.push(spacePart());
displayParts.push(operatorPart(SyntaxKind.EqualsToken));
displayParts.push(spacePart());
displayParts.push(keywordPart(SyntaxKind.RequireKeyword));
displayParts.push(punctuationPart(SyntaxKind.OpenParenToken));
displayParts.push(displayPart(getTextOfNode(getExternalModuleImportEqualsDeclarationExpression(importEqualsDeclaration)), SymbolDisplayPartKind.stringLiteral));
displayParts.push(punctuationPart(SyntaxKind.CloseParenToken));
}
else {
const internalAliasSymbol = typeChecker.getSymbolAtLocation(importEqualsDeclaration.moduleReference);
if (internalAliasSymbol) {
displayParts.push(spacePart());
displayParts.push(operatorPart(SyntaxKind.EqualsToken));
displayParts.push(spacePart());
addFullSymbolName(internalAliasSymbol, enclosingDeclaration);
}
}
return true;
}
});
}
if (!hasAddedSymbolInfo) {
if (symbolKind !== ScriptElementKind.unknown) {
if (type) {
if (isThisExpression) {
addNewLineIfDisplayPartsExist();
displayParts.push(keywordPart(SyntaxKind.ThisKeyword));
}
else {
addPrefixForAnyFunctionOrVar(symbol, symbolKind);
}
// For properties, variables and local vars: show the type
if (symbolKind === ScriptElementKind.memberVariableElement ||
symbolFlags & SymbolFlags.Variable ||
symbolKind === ScriptElementKind.localVariableElement ||
isThisExpression) {
displayParts.push(punctuationPart(SyntaxKind.ColonToken));
displayParts.push(spacePart());
// If the type is type parameter, format it specially
if (type.symbol && type.symbol.flags & SymbolFlags.TypeParameter) {
const typeParameterParts = mapToDisplayParts(writer => {
typeChecker.getSymbolDisplayBuilder().buildTypeParameterDisplay(type, writer, enclosingDeclaration);
});
addRange(displayParts, typeParameterParts);
}
else {
addRange(displayParts, typeToDisplayParts(typeChecker, type, enclosingDeclaration));
}
}
else if (symbolFlags & SymbolFlags.Function ||
symbolFlags & SymbolFlags.Method ||
symbolFlags & SymbolFlags.Constructor ||
symbolFlags & SymbolFlags.Signature ||
symbolFlags & SymbolFlags.Accessor ||
symbolKind === ScriptElementKind.memberFunctionElement) {
const allSignatures = type.getNonNullableType().getCallSignatures();
addSignatureDisplayParts(allSignatures[0], allSignatures);
}
}
}
else {
symbolKind = getSymbolKind(symbol, location);
}
}
if (!documentation) {
documentation = symbol.getDocumentationComment();
}
return { displayParts, documentation, symbolKind };
function addNewLineIfDisplayPartsExist() {
if (displayParts.length) {
displayParts.push(lineBreakPart());
}
}
function addFullSymbolName(symbol: Symbol, enclosingDeclaration?: Node) {
const fullSymbolDisplayParts = symbolToDisplayParts(typeChecker, symbol, enclosingDeclaration || sourceFile, /*meaning*/ undefined,
SymbolFormatFlags.WriteTypeParametersOrArguments | SymbolFormatFlags.UseOnlyExternalAliasing);
addRange(displayParts, fullSymbolDisplayParts);
}
function addPrefixForAnyFunctionOrVar(symbol: Symbol, symbolKind: string) {
addNewLineIfDisplayPartsExist();
if (symbolKind) {
pushTypePart(symbolKind);
displayParts.push(spacePart());
addFullSymbolName(symbol);
}
}
function pushTypePart(symbolKind: string) {
switch (symbolKind) {
case ScriptElementKind.variableElement:
case ScriptElementKind.functionElement:
case ScriptElementKind.letElement:
case ScriptElementKind.constElement:
case ScriptElementKind.constructorImplementationElement:
displayParts.push(textOrKeywordPart(symbolKind));
return;
default:
displayParts.push(punctuationPart(SyntaxKind.OpenParenToken));
displayParts.push(textOrKeywordPart(symbolKind));
displayParts.push(punctuationPart(SyntaxKind.CloseParenToken));
return;
}
}
function addSignatureDisplayParts(signature: Signature, allSignatures: Signature[], flags?: TypeFormatFlags) {
addRange(displayParts, signatureToDisplayParts(typeChecker, signature, enclosingDeclaration, flags | TypeFormatFlags.WriteTypeArgumentsOfSignature));
if (allSignatures.length > 1) {
displayParts.push(spacePart());
displayParts.push(punctuationPart(SyntaxKind.OpenParenToken));
displayParts.push(operatorPart(SyntaxKind.PlusToken));
displayParts.push(displayPart((allSignatures.length - 1).toString(), SymbolDisplayPartKind.numericLiteral));
displayParts.push(spacePart());
displayParts.push(textPart(allSignatures.length === 2 ? "overload" : "overloads"));
displayParts.push(punctuationPart(SyntaxKind.CloseParenToken));
}
documentation = signature.getDocumentationComment();
}
function writeTypeParametersOfSymbol(symbol: Symbol, enclosingDeclaration: Node) {
const typeParameterParts = mapToDisplayParts(writer => {
typeChecker.getSymbolDisplayBuilder().buildTypeParameterDisplayFromSymbol(symbol, writer, enclosingDeclaration);
});
addRange(displayParts, typeParameterParts);
}
}
function getQuickInfoAtPosition(fileName: string, position: number): QuickInfo {
synchronizeHostData();
const sourceFile = getValidSourceFile(fileName);
const node = getTouchingPropertyName(sourceFile, position);
if (node === sourceFile) {
return undefined;
}
if (isLabelName(node)) {
return undefined;
}
const typeChecker = program.getTypeChecker();
const symbol = typeChecker.getSymbolAtLocation(node);
if (!symbol || typeChecker.isUnknownSymbol(symbol)) {
// Try getting just type at this position and show
switch (node.kind) {
case SyntaxKind.Identifier:
case SyntaxKind.PropertyAccessExpression:
case SyntaxKind.QualifiedName:
case SyntaxKind.ThisKeyword:
case SyntaxKind.ThisType:
case SyntaxKind.SuperKeyword:
// For the identifiers/this/super etc get the type at position
const type = typeChecker.getTypeAtLocation(node);
if (type) {
return {
kind: ScriptElementKind.unknown,
kindModifiers: ScriptElementKindModifier.none,
textSpan: createTextSpan(node.getStart(), node.getWidth()),
displayParts: typeToDisplayParts(typeChecker, type, getContainerNode(node)),
documentation: type.symbol ? type.symbol.getDocumentationComment() : undefined
};
}
}
return undefined;
}
const displayPartsDocumentationsAndKind = getSymbolDisplayPartsDocumentationAndSymbolKind(symbol, sourceFile, getContainerNode(node), node);
return {
kind: displayPartsDocumentationsAndKind.symbolKind,
kindModifiers: getSymbolModifiers(symbol),
textSpan: createTextSpan(node.getStart(), node.getWidth()),
displayParts: displayPartsDocumentationsAndKind.displayParts,
documentation: displayPartsDocumentationsAndKind.documentation
};
}
function createDefinitionInfo(node: Node, symbolKind: string, symbolName: string, containerName: string): DefinitionInfo {
return {
fileName: node.getSourceFile().fileName,
textSpan: createTextSpanFromBounds(node.getStart(), node.getEnd()),
kind: symbolKind,
name: symbolName,
containerKind: undefined,
containerName
};
}
function getDefinitionFromSymbol(symbol: Symbol, node: Node): DefinitionInfo[] {
const typeChecker = program.getTypeChecker();
const result: DefinitionInfo[] = [];
const declarations = symbol.getDeclarations();
const symbolName = typeChecker.symbolToString(symbol); // Do not get scoped name, just the name of the symbol
const symbolKind = getSymbolKind(symbol, node);
const containerSymbol = symbol.parent;
const containerName = containerSymbol ? typeChecker.symbolToString(containerSymbol, node) : "";
if (!tryAddConstructSignature(symbol, node, symbolKind, symbolName, containerName, result) &&
!tryAddCallSignature(symbol, node, symbolKind, symbolName, containerName, result)) {
// Just add all the declarations.
forEach(declarations, declaration => {
result.push(createDefinitionInfo(declaration, symbolKind, symbolName, containerName));
});
}
return result;
function tryAddConstructSignature(symbol: Symbol, location: Node, symbolKind: string, symbolName: string, containerName: string, result: DefinitionInfo[]) {
// Applicable only if we are in a new expression, or we are on a constructor declaration
// and in either case the symbol has a construct signature definition, i.e. class
if (isNewExpressionTarget(location) || location.kind === SyntaxKind.ConstructorKeyword) {
if (symbol.flags & SymbolFlags.Class) {
// Find the first class-like declaration and try to get the construct signature.
for (const declaration of symbol.getDeclarations()) {
if (isClassLike(declaration)) {
return tryAddSignature(declaration.members,
/*selectConstructors*/ true,
symbolKind,
symbolName,
containerName,
result);
}
}
Debug.fail("Expected declaration to have at least one class-like declaration");
}
}
return false;
}
function tryAddCallSignature(symbol: Symbol, location: Node, symbolKind: string, symbolName: string, containerName: string, result: DefinitionInfo[]) {
if (isCallExpressionTarget(location) || isNewExpressionTarget(location) || isNameOfFunctionDeclaration(location)) {
return tryAddSignature(symbol.declarations, /*selectConstructors*/ false, symbolKind, symbolName, containerName, result);
}
return false;
}
function tryAddSignature(signatureDeclarations: Declaration[], selectConstructors: boolean, symbolKind: string, symbolName: string, containerName: string, result: DefinitionInfo[]) {
const declarations: Declaration[] = [];
let definition: Declaration;
forEach(signatureDeclarations, d => {
if ((selectConstructors && d.kind === SyntaxKind.Constructor) ||
(!selectConstructors && (d.kind === SyntaxKind.FunctionDeclaration || d.kind === SyntaxKind.MethodDeclaration || d.kind === SyntaxKind.MethodSignature))) {
declarations.push(d);
if ((d).body) definition = d;
}
});
if (definition) {
result.push(createDefinitionInfo(definition, symbolKind, symbolName, containerName));
return true;
}
else if (declarations.length) {
result.push(createDefinitionInfo(lastOrUndefined(declarations), symbolKind, symbolName, containerName));
return true;
}
return false;
}
}
function findReferenceInPosition(refs: FileReference[], pos: number): FileReference {
for (const ref of refs) {
if (ref.pos <= pos && pos < ref.end) {
return ref;
}
}
return undefined;
}
function getDefinitionInfoForFileReference(name: string, targetFileName: string): DefinitionInfo {
return {
fileName: targetFileName,
textSpan: createTextSpanFromBounds(0, 0),
kind: ScriptElementKind.scriptElement,
name: name,
containerName: undefined,
containerKind: undefined
};
}
/// Goto definition
function getDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[] {
synchronizeHostData();
const sourceFile = getValidSourceFile(fileName);
/// Triple slash reference comments
const comment = findReferenceInPosition(sourceFile.referencedFiles, position);
if (comment) {
const referenceFile = tryResolveScriptReference(program, sourceFile, comment);
if (referenceFile) {
return [getDefinitionInfoForFileReference(comment.fileName, referenceFile.fileName)];
}
return undefined;
}
// Type reference directives
const typeReferenceDirective = findReferenceInPosition(sourceFile.typeReferenceDirectives, position);
if (typeReferenceDirective) {
const referenceFile = lookUp(program.getResolvedTypeReferenceDirectives(), typeReferenceDirective.fileName);
if (referenceFile && referenceFile.resolvedFileName) {
return [getDefinitionInfoForFileReference(typeReferenceDirective.fileName, referenceFile.resolvedFileName)];
}
return undefined;
}
const node = getTouchingPropertyName(sourceFile, position);
if (node === sourceFile) {
return undefined;
}
// Labels
if (isJumpStatementTarget(node)) {
const labelName = (node).text;
const label = getTargetLabel((node.parent), (node).text);
return label ? [createDefinitionInfo(label, ScriptElementKind.label, labelName, /*containerName*/ undefined)] : undefined;
}
const typeChecker = program.getTypeChecker();
let symbol = typeChecker.getSymbolAtLocation(node);
// Could not find a symbol e.g. node is string or number keyword,
// or the symbol was an internal symbol and does not have a declaration e.g. undefined symbol
if (!symbol) {
return undefined;
}
// If this is an alias, and the request came at the declaration location
// get the aliased symbol instead. This allows for goto def on an import e.g.
// import {A, B} from "mod";
// to jump to the implementation directly.
if (symbol.flags & SymbolFlags.Alias) {
const declaration = symbol.declarations[0];
// Go to the original declaration for cases:
//
// (1) when the aliased symbol was declared in the location(parent).
// (2) when the aliased symbol is originating from a named import.
//
if (node.kind === SyntaxKind.Identifier &&
(node.parent === declaration ||
(declaration.kind === SyntaxKind.ImportSpecifier && declaration.parent && declaration.parent.kind === SyntaxKind.NamedImports))) {
symbol = typeChecker.getAliasedSymbol(symbol);
}
}
// Because name in short-hand property assignment has two different meanings: property name and property value,
// using go-to-definition at such position should go to the variable declaration of the property value rather than
// go to the declaration of the property name (in this case stay at the same position). However, if go-to-definition
// is performed at the location of property access, we would like to go to definition of the property in the short-hand
// assignment. This case and others are handled by the following code.
if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) {
const shorthandSymbol = typeChecker.getShorthandAssignmentValueSymbol(symbol.valueDeclaration);
if (!shorthandSymbol) {
return [];
}
const shorthandDeclarations = shorthandSymbol.getDeclarations();
const shorthandSymbolKind = getSymbolKind(shorthandSymbol, node);
const shorthandSymbolName = typeChecker.symbolToString(shorthandSymbol);
const shorthandContainerName = typeChecker.symbolToString(symbol.parent, node);
return map(shorthandDeclarations,
declaration => createDefinitionInfo(declaration, shorthandSymbolKind, shorthandSymbolName, shorthandContainerName));
}
return getDefinitionFromSymbol(symbol, node);
}
/// Goto type
function getTypeDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[] {
synchronizeHostData();
const sourceFile = getValidSourceFile(fileName);
const node = getTouchingPropertyName(sourceFile, position);
if (node === sourceFile) {
return undefined;
}
const typeChecker = program.getTypeChecker();
const symbol = typeChecker.getSymbolAtLocation(node);
if (!symbol) {
return undefined;
}
const type = typeChecker.getTypeOfSymbolAtLocation(symbol, node);
if (!type) {
return undefined;
}
if (type.flags & TypeFlags.Union) {
const result: DefinitionInfo[] = [];
forEach((type).types, t => {
if (t.symbol) {
addRange(/*to*/ result, /*from*/ getDefinitionFromSymbol(t.symbol, node));
}
});
return result;
}
if (!type.symbol) {
return undefined;
}
return getDefinitionFromSymbol(type.symbol, node);
}
function getOccurrencesAtPosition(fileName: string, position: number): ReferenceEntry[] {
let results = getOccurrencesAtPositionCore(fileName, position);
if (results) {
const sourceFile = getCanonicalFileName(normalizeSlashes(fileName));
// Get occurrences only supports reporting occurrences for the file queried. So
// filter down to that list.
results = filter(results, r => getCanonicalFileName(ts.normalizeSlashes(r.fileName)) === sourceFile);
}
return results;
}
function getDocumentHighlights(fileName: string, position: number, filesToSearch: string[]): DocumentHighlights[] {
synchronizeHostData();
const sourceFilesToSearch = map(filesToSearch, f => program.getSourceFile(f));
const sourceFile = getValidSourceFile(fileName);
const node = getTouchingWord(sourceFile, position);
if (!node) {
return undefined;
}
return getSemanticDocumentHighlights(node) || getSyntacticDocumentHighlights(node);
function getHighlightSpanForNode(node: Node): HighlightSpan {
const start = node.getStart();
const end = node.getEnd();
return {
fileName: sourceFile.fileName,
textSpan: createTextSpanFromBounds(start, end),
kind: HighlightSpanKind.none
};
}
function getSemanticDocumentHighlights(node: Node): DocumentHighlights[] {
if (node.kind === SyntaxKind.Identifier ||
node.kind === SyntaxKind.ThisKeyword ||
node.kind === SyntaxKind.ThisType ||
node.kind === SyntaxKind.SuperKeyword ||
node.kind === SyntaxKind.StringLiteral ||
isLiteralNameOfPropertyDeclarationOrIndexAccess(node)) {
const referencedSymbols = getReferencedSymbolsForNode(node, sourceFilesToSearch, /*findInStrings*/ false, /*findInComments*/ false);
return convertReferencedSymbols(referencedSymbols);
}
return undefined;
function convertReferencedSymbols(referencedSymbols: ReferencedSymbol[]): DocumentHighlights[] {
if (!referencedSymbols) {
return undefined;
}
const fileNameToDocumentHighlights: Map = {};
const result: DocumentHighlights[] = [];
for (const referencedSymbol of referencedSymbols) {
for (const referenceEntry of referencedSymbol.references) {
const fileName = referenceEntry.fileName;
let documentHighlights = getProperty(fileNameToDocumentHighlights, fileName);
if (!documentHighlights) {
documentHighlights = { fileName, highlightSpans: [] };
fileNameToDocumentHighlights[fileName] = documentHighlights;
result.push(documentHighlights);
}
documentHighlights.highlightSpans.push({
textSpan: referenceEntry.textSpan,
kind: referenceEntry.isWriteAccess ? HighlightSpanKind.writtenReference : HighlightSpanKind.reference
});
}
}
return result;
}
}
function getSyntacticDocumentHighlights(node: Node): DocumentHighlights[] {
const fileName = sourceFile.fileName;
const highlightSpans = getHighlightSpans(node);
if (!highlightSpans || highlightSpans.length === 0) {
return undefined;
}
return [{ fileName, highlightSpans }];
// returns true if 'node' is defined and has a matching 'kind'.
function hasKind(node: Node, kind: SyntaxKind) {
return node !== undefined && node.kind === kind;
}
// Null-propagating 'parent' function.
function parent(node: Node): Node {
return node && node.parent;
}
function getHighlightSpans(node: Node): HighlightSpan[] {
if (node) {
switch (node.kind) {
case SyntaxKind.IfKeyword:
case SyntaxKind.ElseKeyword:
if (hasKind(node.parent, SyntaxKind.IfStatement)) {
return getIfElseOccurrences(node.parent);
}
break;
case SyntaxKind.ReturnKeyword:
if (hasKind(node.parent, SyntaxKind.ReturnStatement)) {
return getReturnOccurrences(node.parent);
}
break;
case SyntaxKind.ThrowKeyword:
if (hasKind(node.parent, SyntaxKind.ThrowStatement)) {
return getThrowOccurrences(node.parent);
}
break;
case SyntaxKind.CatchKeyword:
if (hasKind(parent(parent(node)), SyntaxKind.TryStatement)) {
return getTryCatchFinallyOccurrences(node.parent.parent);
}
break;
case SyntaxKind.TryKeyword:
case SyntaxKind.FinallyKeyword:
if (hasKind(parent(node), SyntaxKind.TryStatement)) {
return getTryCatchFinallyOccurrences(node.parent);
}
break;
case SyntaxKind.SwitchKeyword:
if (hasKind(node.parent, SyntaxKind.SwitchStatement)) {
return getSwitchCaseDefaultOccurrences(node.parent);
}
break;
case SyntaxKind.CaseKeyword:
case SyntaxKind.DefaultKeyword:
if (hasKind(parent(parent(parent(node))), SyntaxKind.SwitchStatement)) {
return getSwitchCaseDefaultOccurrences(node.parent.parent.parent);
}
break;
case SyntaxKind.BreakKeyword:
case SyntaxKind.ContinueKeyword:
if (hasKind(node.parent, SyntaxKind.BreakStatement) || hasKind(node.parent, SyntaxKind.ContinueStatement)) {
return getBreakOrContinueStatementOccurrences(node.parent);
}
break;
case SyntaxKind.ForKeyword:
if (hasKind(node.parent, SyntaxKind.ForStatement) ||
hasKind(node.parent, SyntaxKind.ForInStatement) ||
hasKind(node.parent, SyntaxKind.ForOfStatement)) {
return getLoopBreakContinueOccurrences(node.parent);
}
break;
case SyntaxKind.WhileKeyword:
case SyntaxKind.DoKeyword:
if (hasKind(node.parent, SyntaxKind.WhileStatement) || hasKind(node.parent, SyntaxKind.DoStatement)) {
return getLoopBreakContinueOccurrences(node.parent);
}
break;
case SyntaxKind.ConstructorKeyword:
if (hasKind(node.parent, SyntaxKind.Constructor)) {
return getConstructorOccurrences(node.parent);
}
break;
case SyntaxKind.GetKeyword:
case SyntaxKind.SetKeyword:
if (hasKind(node.parent, SyntaxKind.GetAccessor) || hasKind(node.parent, SyntaxKind.SetAccessor)) {
return getGetAndSetOccurrences(node.parent);
}
break;
default:
if (isModifierKind(node.kind) && node.parent &&
(isDeclaration(node.parent) || node.parent.kind === SyntaxKind.VariableStatement)) {
return getModifierOccurrences(node.kind, node.parent);
}
}
}
return undefined;
}
/**
* Aggregates all throw-statements within this node *without* crossing
* into function boundaries and try-blocks with catch-clauses.
*/
function aggregateOwnedThrowStatements(node: Node): ThrowStatement[] {
const statementAccumulator: ThrowStatement[] = [];
aggregate(node);
return statementAccumulator;
function aggregate(node: Node): void {
if (node.kind === SyntaxKind.ThrowStatement) {
statementAccumulator.push(node);
}
else if (node.kind === SyntaxKind.TryStatement) {
const tryStatement = node;
if (tryStatement.catchClause) {
aggregate(tryStatement.catchClause);
}
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 (!isFunctionLike(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 {
let child: Node = throwStatement;
while (child.parent) {
const parent = child.parent;
if (isFunctionBlock(parent) || 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) {
const tryStatement = parent;
if (tryStatement.tryBlock === child && tryStatement.catchClause) {
return child;
}
}
child = parent;
}
return undefined;
}
function aggregateAllBreakAndContinueStatements(node: Node): BreakOrContinueStatement[] {
const statementAccumulator: BreakOrContinueStatement[] = [];
aggregate(node);
return statementAccumulator;
function aggregate(node: Node): void {
if (node.kind === SyntaxKind.BreakStatement || node.kind === SyntaxKind.ContinueStatement) {
statementAccumulator.push(node);
}
// Do not cross function boundaries.
else if (!isFunctionLike(node)) {
forEachChild(node, aggregate);
}
}
}
function ownsBreakOrContinueStatement(owner: Node, statement: BreakOrContinueStatement): boolean {
const actualOwner = getBreakOrContinueOwner(statement);
return actualOwner && actualOwner === owner;
}
function getBreakOrContinueOwner(statement: BreakOrContinueStatement): Node {
for (let node = statement.parent; node; node = node.parent) {
switch (node.kind) {
case SyntaxKind.SwitchStatement:
if (statement.kind === SyntaxKind.ContinueStatement) {
continue;
}
// Fall through.
case SyntaxKind.ForStatement:
case SyntaxKind.ForInStatement:
case SyntaxKind.ForOfStatement:
case SyntaxKind.WhileStatement:
case SyntaxKind.DoStatement:
if (!statement.label || isLabeledBy(node, statement.label.text)) {
return node;
}
break;
default:
// Don't cross function boundaries.
if (isFunctionLike(node)) {
return undefined;
}
break;
}
}
return undefined;
}
function getModifierOccurrences(modifier: SyntaxKind, declaration: Node): HighlightSpan[] {
const container = declaration.parent;
// Make sure we only highlight the keyword when it makes sense to do so.
if (isAccessibilityModifier(modifier)) {
if (!(container.kind === SyntaxKind.ClassDeclaration ||
container.kind === SyntaxKind.ClassExpression ||
(declaration.kind === SyntaxKind.Parameter && hasKind(container, SyntaxKind.Constructor)))) {
return undefined;
}
}
else if (modifier === SyntaxKind.StaticKeyword) {
if (!(container.kind === SyntaxKind.ClassDeclaration || container.kind === SyntaxKind.ClassExpression)) {
return undefined;
}
}
else if (modifier === SyntaxKind.ExportKeyword || modifier === SyntaxKind.DeclareKeyword) {
if (!(container.kind === SyntaxKind.ModuleBlock || container.kind === SyntaxKind.SourceFile)) {
return undefined;
}
}
else if (modifier === SyntaxKind.AbstractKeyword) {
if (!(container.kind === SyntaxKind.ClassDeclaration || declaration.kind === SyntaxKind.ClassDeclaration)) {
return undefined;
}
}
else {
// unsupported modifier
return undefined;
}
const keywords: Node[] = [];
const modifierFlag: NodeFlags = getFlagFromModifier(modifier);
let nodes: Node[];
switch (container.kind) {
case SyntaxKind.ModuleBlock:
case SyntaxKind.SourceFile:
// Container is either a class declaration or the declaration is a classDeclaration
if (modifierFlag & NodeFlags.Abstract) {
nodes = ((declaration).members).concat(declaration);
}
else {
nodes = (container).statements;
}
break;
case SyntaxKind.Constructor:
nodes = ((container).parameters).concat(
(container.parent).members);
break;
case SyntaxKind.ClassDeclaration:
case SyntaxKind.ClassExpression:
nodes = (container).members;
// If we're an accessibility modifier, we're in an instance member and should search
// the constructor's parameter list for instance members as well.
if (modifierFlag & NodeFlags.AccessibilityModifier) {
const constructor = forEach((container).members, member => {
return member.kind === SyntaxKind.Constructor && member;
});
if (constructor) {
nodes = nodes.concat(constructor.parameters);
}
}
else if (modifierFlag & NodeFlags.Abstract) {
nodes = nodes.concat(container);
}
break;
default:
Debug.fail("Invalid container kind.");
}
forEach(nodes, node => {
if (node.modifiers && node.flags & modifierFlag) {
forEach(node.modifiers, child => pushKeywordIf(keywords, child, modifier));
}
});
return map(keywords, getHighlightSpanForNode);
function getFlagFromModifier(modifier: SyntaxKind) {
switch (modifier) {
case SyntaxKind.PublicKeyword:
return NodeFlags.Public;
case SyntaxKind.PrivateKeyword:
return NodeFlags.Private;
case SyntaxKind.ProtectedKeyword:
return NodeFlags.Protected;
case SyntaxKind.StaticKeyword:
return NodeFlags.Static;
case SyntaxKind.ExportKeyword:
return NodeFlags.Export;
case SyntaxKind.DeclareKeyword:
return NodeFlags.Ambient;
case SyntaxKind.AbstractKeyword:
return NodeFlags.Abstract;
default:
Debug.fail();
}
}
}
function pushKeywordIf(keywordList: Node[], token: Node, ...expected: SyntaxKind[]): boolean {
if (token && contains(expected, token.kind)) {
keywordList.push(token);
return true;
}
return false;
}
function getGetAndSetOccurrences(accessorDeclaration: AccessorDeclaration): HighlightSpan[] {
const keywords: Node[] = [];
tryPushAccessorKeyword(accessorDeclaration.symbol, SyntaxKind.GetAccessor);
tryPushAccessorKeyword(accessorDeclaration.symbol, SyntaxKind.SetAccessor);
return map(keywords, getHighlightSpanForNode);
function tryPushAccessorKeyword(accessorSymbol: Symbol, accessorKind: SyntaxKind): void {
const accessor = getDeclarationOfKind(accessorSymbol, accessorKind);
if (accessor) {
forEach(accessor.getChildren(), child => pushKeywordIf(keywords, child, SyntaxKind.GetKeyword, SyntaxKind.SetKeyword));
}
}
}
function getConstructorOccurrences(constructorDeclaration: ConstructorDeclaration): HighlightSpan[] {
const declarations = constructorDeclaration.symbol.getDeclarations();
const keywords: Node[] = [];
forEach(declarations, declaration => {
forEach(declaration.getChildren(), token => {
return pushKeywordIf(keywords, token, SyntaxKind.ConstructorKeyword);
});
});
return map(keywords, getHighlightSpanForNode);
}
function getLoopBreakContinueOccurrences(loopNode: IterationStatement): HighlightSpan[] {
const keywords: Node[] = [];
if (pushKeywordIf(keywords, loopNode.getFirstToken(), SyntaxKind.ForKeyword, SyntaxKind.WhileKeyword, SyntaxKind.DoKeyword)) {
// If we succeeded and got a do-while loop, then start looking for a 'while' keyword.
if (loopNode.kind === SyntaxKind.DoStatement) {
const loopTokens = loopNode.getChildren();
for (let i = loopTokens.length - 1; i >= 0; i--) {
if (pushKeywordIf(keywords, loopTokens[i], SyntaxKind.WhileKeyword)) {
break;
}
}
}
}
const breaksAndContinues = aggregateAllBreakAndContinueStatements(loopNode.statement);
forEach(breaksAndContinues, statement => {
if (ownsBreakOrContinueStatement(loopNode, statement)) {
pushKeywordIf(keywords, statement.getFirstToken(), SyntaxKind.BreakKeyword, SyntaxKind.ContinueKeyword);
}
});
return map(keywords, getHighlightSpanForNode);
}
function getBreakOrContinueStatementOccurrences(breakOrContinueStatement: BreakOrContinueStatement): HighlightSpan[] {
const owner = getBreakOrContinueOwner(breakOrContinueStatement);
if (owner) {
switch (owner.kind) {
case SyntaxKind.ForStatement:
case SyntaxKind.ForInStatement:
case SyntaxKind.ForOfStatement:
case SyntaxKind.DoStatement:
case SyntaxKind.WhileStatement:
return getLoopBreakContinueOccurrences(owner);
case SyntaxKind.SwitchStatement:
return getSwitchCaseDefaultOccurrences(owner);
}
}
return undefined;
}
function getSwitchCaseDefaultOccurrences(switchStatement: SwitchStatement): HighlightSpan[] {
const keywords: Node[] = [];
pushKeywordIf(keywords, switchStatement.getFirstToken(), SyntaxKind.SwitchKeyword);
// Go through each clause in the switch statement, collecting the 'case'/'default' keywords.
forEach(switchStatement.caseBlock.clauses, clause => {
pushKeywordIf(keywords, clause.getFirstToken(), SyntaxKind.CaseKeyword, SyntaxKind.DefaultKeyword);
const breaksAndContinues = aggregateAllBreakAndContinueStatements(clause);
forEach(breaksAndContinues, statement => {
if (ownsBreakOrContinueStatement(switchStatement, statement)) {
pushKeywordIf(keywords, statement.getFirstToken(), SyntaxKind.BreakKeyword);
}
});
});
return map(keywords, getHighlightSpanForNode);
}
function getTryCatchFinallyOccurrences(tryStatement: TryStatement): HighlightSpan[] {
const keywords: Node[] = [];
pushKeywordIf(keywords, tryStatement.getFirstToken(), SyntaxKind.TryKeyword);
if (tryStatement.catchClause) {
pushKeywordIf(keywords, tryStatement.catchClause.getFirstToken(), SyntaxKind.CatchKeyword);
}
if (tryStatement.finallyBlock) {
const finallyKeyword = findChildOfKind(tryStatement, SyntaxKind.FinallyKeyword, sourceFile);
pushKeywordIf(keywords, finallyKeyword, SyntaxKind.FinallyKeyword);
}
return map(keywords, getHighlightSpanForNode);
}
function getThrowOccurrences(throwStatement: ThrowStatement): HighlightSpan[] {
const owner = getThrowStatementOwner(throwStatement);
if (!owner) {
return undefined;
}
const 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 (isFunctionBlock(owner)) {
forEachReturnStatement(owner, returnStatement => {
pushKeywordIf(keywords, returnStatement.getFirstToken(), SyntaxKind.ReturnKeyword);
});
}
return map(keywords, getHighlightSpanForNode);
}
function getReturnOccurrences(returnStatement: ReturnStatement): HighlightSpan[] {
const func = getContainingFunction(returnStatement);
// If we didn't find a containing function with a block body, bail out.
if (!(func && hasKind(func.body, SyntaxKind.Block))) {
return undefined;
}
const keywords: Node[] = [];
forEachReturnStatement(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, getHighlightSpanForNode);
}
function getIfElseOccurrences(ifStatement: IfStatement): HighlightSpan[] {
const keywords: Node[] = [];
// Traverse upwards through all parent if-statements linked by their else-branches.
while (hasKind(ifStatement.parent, SyntaxKind.IfStatement) && (ifStatement.parent).elseStatement === ifStatement) {
ifStatement = ifStatement.parent;
}
// Now traverse back down through the else branches, aggregating if/else keywords of if-statements.
while (ifStatement) {
const children = ifStatement.getChildren();
pushKeywordIf(keywords, children[0], SyntaxKind.IfKeyword);
// Generally the 'else' keyword is second-to-last, so we traverse backwards.
for (let i = children.length - 1; i >= 0; i--) {
if (pushKeywordIf(keywords, children[i], SyntaxKind.ElseKeyword)) {
break;
}
}
if (!hasKind(ifStatement.elseStatement, SyntaxKind.IfStatement)) {
break;
}
ifStatement = ifStatement.elseStatement;
}
const result: HighlightSpan[] = [];
// We'd like to highlight else/ifs together if they are only separated by whitespace
// (i.e. the keywords are separated by no comments, no newlines).
for (let i = 0; i < keywords.length; i++) {
if (keywords[i].kind === SyntaxKind.ElseKeyword && i < keywords.length - 1) {
const elseKeyword = keywords[i];
const ifKeyword = keywords[i + 1]; // this *should* always be an 'if' keyword.
let shouldCombindElseAndIf = true;
// Avoid recalculating getStart() by iterating backwards.
for (let j = ifKeyword.getStart() - 1; j >= elseKeyword.end; j--) {
if (!isWhiteSpace(sourceFile.text.charCodeAt(j))) {
shouldCombindElseAndIf = false;
break;
}
}
if (shouldCombindElseAndIf) {
result.push({
fileName: fileName,
textSpan: createTextSpanFromBounds(elseKeyword.getStart(), ifKeyword.end),
kind: HighlightSpanKind.reference
});
i++; // skip the next keyword
continue;
}
}
// Ordinary case: just highlight the keyword.
result.push(getHighlightSpanForNode(keywords[i]));
}
return result;
}
}
}
/// References and Occurrences
function getOccurrencesAtPositionCore(fileName: string, position: number): ReferenceEntry[] {
synchronizeHostData();
return convertDocumentHighlights(getDocumentHighlights(fileName, position, [fileName]));
function convertDocumentHighlights(documentHighlights: DocumentHighlights[]): ReferenceEntry[] {
if (!documentHighlights) {
return undefined;
}
const result: ReferenceEntry[] = [];
for (const entry of documentHighlights) {
for (const highlightSpan of entry.highlightSpans) {
result.push({
fileName: entry.fileName,
textSpan: highlightSpan.textSpan,
isWriteAccess: highlightSpan.kind === HighlightSpanKind.writtenReference
});
}
}
return result;
}
}
function convertReferences(referenceSymbols: ReferencedSymbol[]): ReferenceEntry[] {
if (!referenceSymbols) {
return undefined;
}
const referenceEntries: ReferenceEntry[] = [];
for (const referenceSymbol of referenceSymbols) {
addRange(referenceEntries, referenceSymbol.references);
}
return referenceEntries;
}
function findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): RenameLocation[] {
const referencedSymbols = findReferencedSymbols(fileName, position, findInStrings, findInComments);
return convertReferences(referencedSymbols);
}
function getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[] {
const referencedSymbols = findReferencedSymbols(fileName, position, /*findInStrings*/ false, /*findInComments*/ false);
return convertReferences(referencedSymbols);
}
function findReferences(fileName: string, position: number): ReferencedSymbol[] {
const referencedSymbols = findReferencedSymbols(fileName, position, /*findInStrings*/ false, /*findInComments*/ false);
// Only include referenced symbols that have a valid definition.
return filter(referencedSymbols, rs => !!rs.definition);
}
function findReferencedSymbols(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): ReferencedSymbol[] {
synchronizeHostData();
const sourceFile = getValidSourceFile(fileName);
const node = getTouchingPropertyName(sourceFile, position, /*includeJsDocComment*/ true);
if (node === sourceFile) {
return undefined;
}
if (node.kind !== SyntaxKind.Identifier &&
// TODO (drosen): This should be enabled in a later release - currently breaks rename.
// node.kind !== SyntaxKind.ThisKeyword &&
// node.kind !== SyntaxKind.SuperKeyword &&
node.kind !== SyntaxKind.StringLiteral &&
!isLiteralNameOfPropertyDeclarationOrIndexAccess(node)) {
return undefined;
}
Debug.assert(node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.NumericLiteral || node.kind === SyntaxKind.StringLiteral);
return getReferencedSymbolsForNode(node, program.getSourceFiles(), findInStrings, findInComments);
}
function getReferencedSymbolsForNode(node: Node, sourceFiles: SourceFile[], findInStrings: boolean, findInComments: boolean): ReferencedSymbol[] {
const typeChecker = program.getTypeChecker();
// Labels
if (isLabelName(node)) {
if (isJumpStatementTarget(node)) {
const labelDefinition = getTargetLabel((node.parent), (node).text);
// if we have a label definition, look within its statement for references, if not, then
// the label is undefined and we have no results..
return labelDefinition ? getLabelReferencesInNode(labelDefinition.parent, labelDefinition) : undefined;
}
else {
// it is a label definition and not a target, search within the parent labeledStatement
return getLabelReferencesInNode(node.parent, node);
}
}
if (node.kind === SyntaxKind.ThisKeyword || node.kind === SyntaxKind.ThisType) {
return getReferencesForThisKeyword(node, sourceFiles);
}
if (node.kind === SyntaxKind.SuperKeyword) {
return getReferencesForSuperKeyword(node);
}
const symbol = typeChecker.getSymbolAtLocation(node);
if (!symbol && node.kind === SyntaxKind.StringLiteral) {
return getReferencesForStringLiteral(node, sourceFiles);
}
// Could not find a symbol e.g. unknown identifier
if (!symbol) {
// Can't have references to something that we have no symbol for.
return undefined;
}
const declarations = symbol.declarations;
// The symbol was an internal symbol and does not have a declaration e.g. undefined symbol
if (!declarations || !declarations.length) {
return undefined;
}
let result: ReferencedSymbol[];
// Compute the meaning from the location and the symbol it references
const searchMeaning = getIntersectingMeaningFromDeclarations(getMeaningFromLocation(node), declarations);
// Get the text to search for.
// Note: if this is an external module symbol, the name doesn't include quotes.
const declaredName = stripQuotes(getDeclaredName(typeChecker, symbol, node));
// Try to get the smallest valid scope that we can limit our search to;
// otherwise we'll need to search globally (i.e. include each file).
const scope = getSymbolScope(symbol);
// Maps from a symbol ID to the ReferencedSymbol entry in 'result'.
const symbolToIndex: number[] = [];
if (scope) {
result = [];
getReferencesInNode(scope, symbol, declaredName, node, searchMeaning, findInStrings, findInComments, result, symbolToIndex);
}
else {
const internedName = getInternedName(symbol, node, declarations);
for (const sourceFile of sourceFiles) {
cancellationToken.throwIfCancellationRequested();
const nameTable = getNameTable(sourceFile);
if (lookUp(nameTable, internedName) !== undefined) {
result = result || [];
getReferencesInNode(sourceFile, symbol, declaredName, node, searchMeaning, findInStrings, findInComments, result, symbolToIndex);
}
}
}
return result;
function getDefinition(symbol: Symbol): DefinitionInfo {
const info = getSymbolDisplayPartsDocumentationAndSymbolKind(symbol, node.getSourceFile(), getContainerNode(node), node);
const name = map(info.displayParts, p => p.text).join("");
const declarations = symbol.declarations;
if (!declarations || declarations.length === 0) {
return undefined;
}
return {
containerKind: "",
containerName: "",
name,
kind: info.symbolKind,
fileName: declarations[0].getSourceFile().fileName,
textSpan: createTextSpan(declarations[0].getStart(), 0)
};
}
function getAliasSymbolForPropertyNameSymbol(symbol: Symbol, location: Node): Symbol {
if (symbol.flags & SymbolFlags.Alias) {
// Default import get alias
const defaultImport = getDeclarationOfKind(symbol, SyntaxKind.ImportClause);
if (defaultImport) {
return typeChecker.getAliasedSymbol(symbol);
}
const importOrExportSpecifier = forEach(symbol.declarations,
declaration => (declaration.kind === SyntaxKind.ImportSpecifier ||
declaration.kind === SyntaxKind.ExportSpecifier) ? declaration : undefined);
if (importOrExportSpecifier &&
// export { a }
(!importOrExportSpecifier.propertyName ||
// export {a as class } where a is location
importOrExportSpecifier.propertyName === location)) {
// If Import specifier -> get alias
// else Export specifier -> get local target
return importOrExportSpecifier.kind === SyntaxKind.ImportSpecifier ?
typeChecker.getAliasedSymbol(symbol) :
typeChecker.getExportSpecifierLocalTargetSymbol(importOrExportSpecifier);
}
}
return undefined;
}
function getPropertySymbolOfDestructuringAssignment(location: Node) {
return isArrayLiteralOrObjectLiteralDestructuringPattern(location.parent.parent) &&
typeChecker.getPropertySymbolOfDestructuringAssignment(location);
}
function isObjectBindingPatternElementWithoutPropertyName(symbol: Symbol) {
const bindingElement = getDeclarationOfKind(symbol, SyntaxKind.BindingElement);
return bindingElement &&
bindingElement.parent.kind === SyntaxKind.ObjectBindingPattern &&
!bindingElement.propertyName;
}
function getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol: Symbol) {
if (isObjectBindingPatternElementWithoutPropertyName(symbol)) {
const bindingElement = getDeclarationOfKind(symbol, SyntaxKind.BindingElement);
const typeOfPattern = typeChecker.getTypeAtLocation(bindingElement.parent);
return typeOfPattern && typeChecker.getPropertyOfType(typeOfPattern, (bindingElement.name).text);
}
return undefined;
}
function getInternedName(symbol: Symbol, location: Node, declarations: Declaration[]): string {
// If this is an export or import specifier it could have been renamed using the 'as' syntax.
// If so we want to search for whatever under the cursor.
if (isImportOrExportSpecifierName(location)) {
return location.getText();
}
// Try to get the local symbol if we're dealing with an 'export default'
// since that symbol has the "true" name.
const localExportDefaultSymbol = getLocalSymbolForExportDefault(symbol);
symbol = localExportDefaultSymbol || symbol;
return stripQuotes(symbol.name);
}
/**
* Determines the smallest scope in which a symbol may have named references.
* Note that not every construct has been accounted for. This function can
* probably be improved.
*
* @returns undefined if the scope cannot be determined, implying that
* a reference to a symbol can occur anywhere.
*/
function getSymbolScope(symbol: Symbol): Node {
// If this is the symbol of a named function expression or named class expression,
// then named references are limited to its own scope.
const valueDeclaration = symbol.valueDeclaration;
if (valueDeclaration && (valueDeclaration.kind === SyntaxKind.FunctionExpression || valueDeclaration.kind === SyntaxKind.ClassExpression)) {
return valueDeclaration;
}
// If this is private property or method, the scope is the containing class
if (symbol.flags & (SymbolFlags.Property | SymbolFlags.Method)) {
const privateDeclaration = forEach(symbol.getDeclarations(), d => (d.flags & NodeFlags.Private) ? d : undefined);
if (privateDeclaration) {
return getAncestor(privateDeclaration, SyntaxKind.ClassDeclaration);
}
}
// If the symbol is an import we would like to find it if we are looking for what it imports.
// So consider it visible outside its declaration scope.
if (symbol.flags & SymbolFlags.Alias) {
return undefined;
}
// If symbol is of object binding pattern element without property name we would want to
// look for property too and that could be anywhere
if (isObjectBindingPatternElementWithoutPropertyName(symbol)) {
return undefined;
}
// if this symbol is visible from its parent container, e.g. exported, then bail out
// if symbol correspond to the union property - bail out
if (symbol.parent || (symbol.flags & SymbolFlags.SyntheticProperty)) {
return undefined;
}
let scope: Node;
const declarations = symbol.getDeclarations();
if (declarations) {
for (const declaration of declarations) {
const container = getContainerNode(declaration);
if (!container) {
return undefined;
}
if (scope && scope !== container) {
// Different declarations have different containers, bail out
return undefined;
}
if (container.kind === SyntaxKind.SourceFile && !isExternalModule(container)) {
// This is a global variable and not an external module, any declaration defined
// within this scope is visible outside the file
return undefined;
}
// The search scope is the container node
scope = container;
}
}
return scope;
}
function getPossibleSymbolReferencePositions(sourceFile: SourceFile, symbolName: string, start: number, end: number): number[] {
const positions: number[] = [];
/// TODO: Cache symbol existence for files to save text search
// Also, need to make this work for unicode escapes.
// Be resilient in the face of a symbol with no name or zero length name
if (!symbolName || !symbolName.length) {
return positions;
}
const text = sourceFile.text;
const sourceLength = text.length;
const symbolNameLength = symbolName.length;
let position = text.indexOf(symbolName, start);
while (position >= 0) {
cancellationToken.throwIfCancellationRequested();
// If we are past the end, stop looking
if (position > end) break;
// We found a match. Make sure it's not part of a larger word (i.e. the char
// before and after it have to be a non-identifier char).
const endPosition = position + symbolNameLength;
if ((position === 0 || !isIdentifierPart(text.charCodeAt(position - 1), ScriptTarget.Latest)) &&
(endPosition === sourceLength || !isIdentifierPart(text.charCodeAt(endPosition), ScriptTarget.Latest))) {
// Found a real match. Keep searching.
positions.push(position);
}
position = text.indexOf(symbolName, position + symbolNameLength + 1);
}
return positions;
}
function getLabelReferencesInNode(container: Node, targetLabel: Identifier): ReferencedSymbol[] {
const references: ReferenceEntry[] = [];
const sourceFile = container.getSourceFile();
const labelName = targetLabel.text;
const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, labelName, container.getStart(), container.getEnd());
forEach(possiblePositions, position => {
cancellationToken.throwIfCancellationRequested();
const node = getTouchingWord(sourceFile, position);
if (!node || node.getWidth() !== labelName.length) {
return;
}
// Only pick labels that are either the target label, or have a target that is the target label
if (node === targetLabel ||
(isJumpStatementTarget(node) && getTargetLabel(node, labelName) === targetLabel)) {
references.push(getReferenceEntryFromNode(node));
}
});
const definition: DefinitionInfo = {
containerKind: "",
containerName: "",
fileName: targetLabel.getSourceFile().fileName,
kind: ScriptElementKind.label,
name: labelName,
textSpan: createTextSpanFromBounds(targetLabel.getStart(), targetLabel.getEnd())
};
return [{ definition, references }];
}
function isValidReferencePosition(node: Node, searchSymbolName: string): boolean {
if (node) {
// Compare the length so we filter out strict superstrings of the symbol we are looking for
switch (node.kind) {
case SyntaxKind.Identifier:
return node.getWidth() === searchSymbolName.length;
case SyntaxKind.StringLiteral:
if (isLiteralNameOfPropertyDeclarationOrIndexAccess(node) ||
isNameOfExternalModuleImportOrDeclaration(node)) {
// For string literals we have two additional chars for the quotes
return node.getWidth() === searchSymbolName.length + 2;
}
break;
case SyntaxKind.NumericLiteral:
if (isLiteralNameOfPropertyDeclarationOrIndexAccess(node)) {
return node.getWidth() === searchSymbolName.length;
}
break;
}
}
return false;
}
/** Search within node "container" for references for a search value, where the search value is defined as a
* tuple of(searchSymbol, searchText, searchLocation, and searchMeaning).
* searchLocation: a node where the search value
*/
function getReferencesInNode(container: Node,
searchSymbol: Symbol,
searchText: string,
searchLocation: Node,
searchMeaning: SemanticMeaning,
findInStrings: boolean,
findInComments: boolean,
result: ReferencedSymbol[],
symbolToIndex: number[]): void {
const sourceFile = container.getSourceFile();
const tripleSlashDirectivePrefixRegex = /^\/\/\/\s*;
const start = findInComments ? container.getFullStart() : container.getStart();
const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, searchText, start, container.getEnd());
if (possiblePositions.length) {
// Build the set of symbols to search for, initially it has only the current symbol
const searchSymbols = populateSearchSymbolSet(searchSymbol, searchLocation);
forEach(possiblePositions, position => {
cancellationToken.throwIfCancellationRequested();
const referenceLocation = getTouchingPropertyName(sourceFile, position);
if (!isValidReferencePosition(referenceLocation, searchText)) {
// This wasn't the start of a token. Check to see if it might be a
// match in a comment or string if that's what the caller is asking
// for.
if ((findInStrings && isInString(sourceFile, position)) ||
(findInComments && isInNonReferenceComment(sourceFile, position))) {
// In the case where we're looking inside comments/strings, we don't have
// an actual definition. So just use 'undefined' here. Features like
// 'Rename' won't care (as they ignore the definitions), and features like
// 'FindReferences' will just filter out these results.
result.push({
definition: undefined,
references: [{
fileName: sourceFile.fileName,
textSpan: createTextSpan(position, searchText.length),
isWriteAccess: false
}]
});
}
return;
}
if (!(getMeaningFromLocation(referenceLocation) & searchMeaning)) {
return;
}
const referenceSymbol = typeChecker.getSymbolAtLocation(referenceLocation);
if (referenceSymbol) {
const referenceSymbolDeclaration = referenceSymbol.valueDeclaration;
const shorthandValueSymbol = typeChecker.getShorthandAssignmentValueSymbol(referenceSymbolDeclaration);
const relatedSymbol = getRelatedSymbol(searchSymbols, referenceSymbol, referenceLocation);
if (relatedSymbol) {
const referencedSymbol = getReferencedSymbol(relatedSymbol);
referencedSymbol.references.push(getReferenceEntryFromNode(referenceLocation));
}
/* Because in short-hand property assignment, an identifier which stored as name of the short-hand property assignment
* has two meaning : property name and property value. Therefore when we do findAllReference at the position where
* an identifier is declared, the language service should return the position of the variable declaration as well as
* the position in short-hand property assignment excluding property accessing. However, if we do findAllReference at the
* position of property accessing, the referenceEntry of such position will be handled in the first case.
*/
else if (!(referenceSymbol.flags & SymbolFlags.Transient) && searchSymbols.indexOf(shorthandValueSymbol) >= 0) {
const referencedSymbol = getReferencedSymbol(shorthandValueSymbol);
referencedSymbol.references.push(getReferenceEntryFromNode(referenceSymbolDeclaration.name));
}
}
});
}
return;
function getReferencedSymbol(symbol: Symbol): ReferencedSymbol {
const symbolId = getSymbolId(symbol);
let index = symbolToIndex[symbolId];
if (index === undefined) {
index = result.length;
symbolToIndex[symbolId] = index;
result.push({
definition: getDefinition(symbol),
references: []
});
}
return result[index];
}
function isInNonReferenceComment(sourceFile: SourceFile, position: number): boolean {
return isInCommentHelper(sourceFile, position, isNonReferenceComment);
function isNonReferenceComment(c: CommentRange): boolean {
const commentText = sourceFile.text.substring(c.pos, c.end);
return !tripleSlashDirectivePrefixRegex.test(commentText);
}
}
}
function getReferencesForSuperKeyword(superKeyword: Node): ReferencedSymbol[] {
let searchSpaceNode = getSuperContainer(superKeyword, /*stopOnFunctions*/ false);
if (!searchSpaceNode) {
return undefined;
}
// Whether 'super' occurs in a static context within a class.
let staticFlag = NodeFlags.Static;
switch (searchSpaceNode.kind) {
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.PropertySignature:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.MethodSignature:
case SyntaxKind.Constructor:
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
staticFlag &= searchSpaceNode.flags;
searchSpaceNode = searchSpaceNode.parent; // re-assign to be the owning class
break;
default:
return undefined;
}
const references: ReferenceEntry[] = [];
const sourceFile = searchSpaceNode.getSourceFile();
const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, "super", searchSpaceNode.getStart(), searchSpaceNode.getEnd());
forEach(possiblePositions, position => {
cancellationToken.throwIfCancellationRequested();
const node = getTouchingWord(sourceFile, position);
if (!node || node.kind !== SyntaxKind.SuperKeyword) {
return;
}
const container = getSuperContainer(node, /*stopOnFunctions*/ false);
// If we have a 'super' container, we must have an enclosing class.
// Now make sure the owning class is the same as the search-space
// and has the same static qualifier as the original 'super's owner.
if (container && (NodeFlags.Static & container.flags) === staticFlag && container.parent.symbol === searchSpaceNode.symbol) {
references.push(getReferenceEntryFromNode(node));
}
});
const definition = getDefinition(searchSpaceNode.symbol);
return [{ definition, references }];
}
function getReferencesForThisKeyword(thisOrSuperKeyword: Node, sourceFiles: SourceFile[]): ReferencedSymbol[] {
let searchSpaceNode = getThisContainer(thisOrSuperKeyword, /* includeArrowFunctions */ false);
// Whether 'this' occurs in a static context within a class.
let staticFlag = NodeFlags.Static;
switch (searchSpaceNode.kind) {
case SyntaxKind.MethodDeclaration:
case SyntaxKind.MethodSignature:
if (isObjectLiteralMethod(searchSpaceNode)) {
break;
}
// fall through
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.PropertySignature:
case SyntaxKind.Constructor:
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
staticFlag &= searchSpaceNode.flags;
searchSpaceNode = searchSpaceNode.parent; // re-assign to be the owning class
break;
case SyntaxKind.SourceFile:
if (isExternalModule(searchSpaceNode)) {
return undefined;
}
// Fall through
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.FunctionExpression:
break;
// Computed properties in classes are not handled here because references to this are illegal,
// so there is no point finding references to them.
default:
return undefined;
}
const references: ReferenceEntry[] = [];
let possiblePositions: number[];
if (searchSpaceNode.kind === SyntaxKind.SourceFile) {
forEach(sourceFiles, sourceFile => {
possiblePositions = getPossibleSymbolReferencePositions(sourceFile, "this", sourceFile.getStart(), sourceFile.getEnd());
getThisReferencesInFile(sourceFile, sourceFile, possiblePositions, references);
});
}
else {
const sourceFile = searchSpaceNode.getSourceFile();
possiblePositions = getPossibleSymbolReferencePositions(sourceFile, "this", searchSpaceNode.getStart(), searchSpaceNode.getEnd());
getThisReferencesInFile(sourceFile, searchSpaceNode, possiblePositions, references);
}
return [{
definition: {
containerKind: "",
containerName: "",
fileName: node.getSourceFile().fileName,
kind: ScriptElementKind.variableElement,
name: "this",
textSpan: createTextSpanFromBounds(node.getStart(), node.getEnd())
},
references: references
}];
function getThisReferencesInFile(sourceFile: SourceFile, searchSpaceNode: Node, possiblePositions: number[], result: ReferenceEntry[]): void {
forEach(possiblePositions, position => {
cancellationToken.throwIfCancellationRequested();
const node = getTouchingWord(sourceFile, position);
if (!node || (node.kind !== SyntaxKind.ThisKeyword && node.kind !== SyntaxKind.ThisType)) {
return;
}
const container = getThisContainer(node, /* includeArrowFunctions */ false);
switch (searchSpaceNode.kind) {
case SyntaxKind.FunctionExpression:
case SyntaxKind.FunctionDeclaration:
if (searchSpaceNode.symbol === container.symbol) {
result.push(getReferenceEntryFromNode(node));
}
break;
case SyntaxKind.MethodDeclaration:
case SyntaxKind.MethodSignature:
if (isObjectLiteralMethod(searchSpaceNode) && searchSpaceNode.symbol === container.symbol) {
result.push(getReferenceEntryFromNode(node));
}
break;
case SyntaxKind.ClassExpression:
case SyntaxKind.ClassDeclaration:
// Make sure the container belongs to the same class
// and has the appropriate static modifier from the original container.
if (container.parent && searchSpaceNode.symbol === container.parent.symbol && (container.flags & NodeFlags.Static) === staticFlag) {
result.push(getReferenceEntryFromNode(node));
}
break;
case SyntaxKind.SourceFile:
if (container.kind === SyntaxKind.SourceFile && !isExternalModule(container)) {
result.push(getReferenceEntryFromNode(node));
}
break;
}
});
}
}
function getReferencesForStringLiteral(node: StringLiteral, sourceFiles: SourceFile[]): ReferencedSymbol[] {
const typeChecker = program.getTypeChecker();
const type = getStringLiteralTypeForNode(node, typeChecker);
if (!type) {
// nothing to do here. moving on
return undefined;
}
const references: ReferenceEntry[] = [];
for (const sourceFile of sourceFiles) {
const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, type.text, sourceFile.getStart(), sourceFile.getEnd());
getReferencesForStringLiteralInFile(sourceFile, type, possiblePositions, references);
}
return [{
definition: {
containerKind: "",
containerName: "",
fileName: node.getSourceFile().fileName,
kind: ScriptElementKind.variableElement,
name: type.text,
textSpan: createTextSpanFromBounds(node.getStart(), node.getEnd())
},
references: references
}];
function getReferencesForStringLiteralInFile(sourceFile: SourceFile, searchType: Type, possiblePositions: number[], references: ReferenceEntry[]): void {
for (const position of possiblePositions) {
cancellationToken.throwIfCancellationRequested();
const node = getTouchingWord(sourceFile, position);
if (!node || node.kind !== SyntaxKind.StringLiteral) {
return;
}
const type = getStringLiteralTypeForNode(node, typeChecker);
if (type === searchType) {
references.push(getReferenceEntryFromNode(node));
}
}
}
}
function populateSearchSymbolSet(symbol: Symbol, location: Node): Symbol[] {
// The search set contains at least the current symbol
let result = [symbol];
// If the location is name of property symbol from object literal destructuring pattern
// Search the property symbol
// for ( { property: p2 } of elems) { }
const containingObjectLiteralElement = getContainingObjectLiteralElement(location);
if (containingObjectLiteralElement && containingObjectLiteralElement.kind !== SyntaxKind.ShorthandPropertyAssignment) {
const propertySymbol = getPropertySymbolOfDestructuringAssignment(location);
if (propertySymbol) {
result.push(propertySymbol);
}
}
// If the symbol is an alias, add what it aliases to the list
// import {a} from "mod";
// export {a}
// If the symbol is an alias to default declaration, add what it aliases to the list
// declare "mod" { export default class B { } }
// import B from "mod";
//// For export specifiers, the exported name can be referring to a local symbol, e.g.:
//// import {a} from "mod";
//// export {a as somethingElse}
//// We want the *local* declaration of 'a' as declared in the import,
//// *not* as declared within "mod" (or farther)
const aliasSymbol = getAliasSymbolForPropertyNameSymbol(symbol, location);
if (aliasSymbol) {
result = result.concat(populateSearchSymbolSet(aliasSymbol, location));
}
// If the location is in a context sensitive location (i.e. in an object literal) try
// to get a contextual type for it, and add the property symbol from the contextual
// type to the search set
if (containingObjectLiteralElement) {
forEach(getPropertySymbolsFromContextualType(containingObjectLiteralElement), contextualSymbol => {
addRange(result, typeChecker.getRootSymbols(contextualSymbol));
});
/* Because in short-hand property assignment, location has two meaning : property name and as value of the property
* When we do findAllReference at the position of the short-hand property assignment, we would want to have references to position of
* property name and variable declaration of the identifier.
* Like in below example, when querying for all references for an identifier 'name', of the property assignment, the language service
* should show both 'name' in 'obj' and 'name' in variable declaration
* const name = "Foo";
* const obj = { name };
* In order to do that, we will populate the search set with the value symbol of the identifier as a value of the property assignment
* so that when matching with potential reference symbol, both symbols from property declaration and variable declaration
* will be included correctly.
*/
const shorthandValueSymbol = typeChecker.getShorthandAssignmentValueSymbol(location.parent);
if (shorthandValueSymbol) {
result.push(shorthandValueSymbol);
}
}
// If the symbol.valueDeclaration is a property parameter declaration,
// we should include both parameter declaration symbol and property declaration symbol
// Parameter Declaration symbol is only visible within function scope, so the symbol is stored in constructor.locals.
// Property Declaration symbol is a member of the class, so the symbol is stored in its class Declaration.symbol.members
if (symbol.valueDeclaration && symbol.valueDeclaration.kind === SyntaxKind.Parameter &&
isParameterPropertyDeclaration(symbol.valueDeclaration)) {
result = result.concat(typeChecker.getSymbolsOfParameterPropertyDeclaration(symbol.valueDeclaration, symbol.name));
}
// If this is symbol of binding element without propertyName declaration in Object binding pattern
// Include the property in the search
const bindingElementPropertySymbol = getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol);
if (bindingElementPropertySymbol) {
result.push(bindingElementPropertySymbol);
}
// If this is a union property, add all the symbols from all its source symbols in all unioned types.
// If the symbol is an instantiation from a another symbol (e.g. widened symbol) , add the root the list
forEach(typeChecker.getRootSymbols(symbol), rootSymbol => {
if (rootSymbol !== symbol) {
result.push(rootSymbol);
}
// Add symbol of properties/methods of the same name in base classes and implemented interfaces definitions
if (rootSymbol.parent && rootSymbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface)) {
getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.getName(), result, /*previousIterationSymbolsCache*/ {});
}
});
return result;
}
/**
* Find symbol of the given property-name and add the symbol to the given result array
* @param symbol a symbol to start searching for the given propertyName
* @param propertyName a name of property to search for
* @param result an array of symbol of found property symbols
* @param previousIterationSymbolsCache a cache of symbol from previous iterations of calling this function to prevent infinite revisiting of the same symbol.
* The value of previousIterationSymbol is undefined when the function is first called.
*/
function getPropertySymbolsFromBaseTypes(symbol: Symbol, propertyName: string, result: Symbol[],
previousIterationSymbolsCache: SymbolTable): void {
if (!symbol) {
return;
}
// If the current symbol is the same as the previous-iteration symbol, we can just return the symbol that has already been visited
// This is particularly important for the following cases, so that we do not infinitely visit the same symbol.
// For example:
// interface C extends C {
// /*findRef*/propName: string;
// }
// The first time getPropertySymbolsFromBaseTypes is called when finding-all-references at propName,
// the symbol argument will be the symbol of an interface "C" and previousIterationSymbol is undefined,
// the function will add any found symbol of the property-name, then its sub-routine will call
// getPropertySymbolsFromBaseTypes again to walk up any base types to prevent revisiting already
// visited symbol, interface "C", the sub-routine will pass the current symbol as previousIterationSymbol.
if (hasProperty(previousIterationSymbolsCache, symbol.name)) {
return;
}
if (symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) {
forEach(symbol.getDeclarations(), declaration => {
if (isClassLike(declaration)) {
getPropertySymbolFromTypeReference(getClassExtendsHeritageClauseElement(declaration));
forEach(getClassImplementsHeritageClauseElements(declaration), getPropertySymbolFromTypeReference);
}
else if (declaration.kind === SyntaxKind.InterfaceDeclaration) {
forEach(getInterfaceBaseTypeNodes(declaration), getPropertySymbolFromTypeReference);
}
});
}
return;
function getPropertySymbolFromTypeReference(typeReference: ExpressionWithTypeArguments) {
if (typeReference) {
const type = typeChecker.getTypeAtLocation(typeReference);
if (type) {
const propertySymbol = typeChecker.getPropertyOfType(type, propertyName);
if (propertySymbol) {
result.push(...typeChecker.getRootSymbols(propertySymbol));
}
// Visit the typeReference as well to see if it directly or indirectly use that property
previousIterationSymbolsCache[symbol.name] = symbol;
getPropertySymbolsFromBaseTypes(type.symbol, propertyName, result, previousIterationSymbolsCache);
}
}
}
}
function getRelatedSymbol(searchSymbols: Symbol[], referenceSymbol: Symbol, referenceLocation: Node): Symbol {
if (searchSymbols.indexOf(referenceSymbol) >= 0) {
return referenceSymbol;
}
// If the reference symbol is an alias, check if what it is aliasing is one of the search
// symbols but by looking up for related symbol of this alias so it can handle multiple level of indirectness.
const aliasSymbol = getAliasSymbolForPropertyNameSymbol(referenceSymbol, referenceLocation);
if (aliasSymbol) {
return getRelatedSymbol(searchSymbols, aliasSymbol, referenceLocation);
}
// If the reference location is in an object literal, try to get the contextual type for the
// object literal, lookup the property symbol in the contextual type, and use this symbol to
// compare to our searchSymbol
const containingObjectLiteralElement = getContainingObjectLiteralElement(referenceLocation);
if (containingObjectLiteralElement) {
const contextualSymbol = forEach(getPropertySymbolsFromContextualType(containingObjectLiteralElement), contextualSymbol => {
return forEach(typeChecker.getRootSymbols(contextualSymbol), s => searchSymbols.indexOf(s) >= 0 ? s : undefined);
});
if (contextualSymbol) {
return contextualSymbol;
}
// If the reference location is the name of property from object literal destructuring pattern
// Get the property symbol from the object literal's type and look if thats the search symbol
// In below eg. get 'property' from type of elems iterating type
// for ( { property: p2 } of elems) { }
const propertySymbol = getPropertySymbolOfDestructuringAssignment(referenceLocation);
if (propertySymbol && searchSymbols.indexOf(propertySymbol) >= 0) {
return propertySymbol;
}
}
// If the reference location is the binding element and doesn't have property name
// then include the binding element in the related symbols
// let { a } : { a };
const bindingElementPropertySymbol = getPropertySymbolOfObjectBindingPatternWithoutPropertyName(referenceSymbol);
if (bindingElementPropertySymbol && searchSymbols.indexOf(bindingElementPropertySymbol) >= 0) {
return bindingElementPropertySymbol;
}
// Unwrap symbols to get to the root (e.g. transient symbols as a result of widening)
// Or a union property, use its underlying unioned symbols
return forEach(typeChecker.getRootSymbols(referenceSymbol), rootSymbol => {
// if it is in the list, then we are done
if (searchSymbols.indexOf(rootSymbol) >= 0) {
return rootSymbol;
}
// Finally, try all properties with the same name in any type the containing type extended or implemented, and
// see if any is in the list
if (rootSymbol.parent && rootSymbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface)) {
const result: Symbol[] = [];
getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.getName(), result, /*previousIterationSymbolsCache*/ {});
return forEach(result, s => searchSymbols.indexOf(s) >= 0 ? s : undefined);
}
return undefined;
});
}
function getNameFromObjectLiteralElement(node: ObjectLiteralElement) {
if (node.name.kind === SyntaxKind.ComputedPropertyName) {
const nameExpression = (node.name).expression;
// treat computed property names where expression is string/numeric literal as just string/numeric literal
if (isStringOrNumericLiteral(nameExpression.kind)) {
return (nameExpression).text;
}
return undefined;
}
return (node.name).text;
}
function getPropertySymbolsFromContextualType(node: ObjectLiteralElement): Symbol[] {
const objectLiteral = node.parent;
const contextualType = typeChecker.getContextualType(objectLiteral);
const name = getNameFromObjectLiteralElement(node);
if (name && contextualType) {
const result: Symbol[] = [];
const symbol = contextualType.getProperty(name);
if (symbol) {
result.push(symbol);
}
if (contextualType.flags & TypeFlags.Union) {
forEach((contextualType).types, t => {
const symbol = t.getProperty(name);
if (symbol) {
result.push(symbol);
}
});
}
return result;
}
return undefined;
}
/** Given an initial searchMeaning, extracted from a location, widen the search scope based on the declarations
* of the corresponding symbol. e.g. if we are searching for "Foo" in value position, but "Foo" references a class
* then we need to widen the search to include type positions as well.
* On the contrary, if we are searching for "Bar" in type position and we trace bar to an interface, and an uninstantiated
* module, we want to keep the search limited to only types, as the two declarations (interface and uninstantiated module)
* do not intersect in any of the three spaces.
*/
function getIntersectingMeaningFromDeclarations(meaning: SemanticMeaning, declarations: Declaration[]): SemanticMeaning {
if (declarations) {
let lastIterationMeaning: SemanticMeaning;
do {
// The result is order-sensitive, for instance if initialMeaning === Namespace, and declarations = [class, instantiated module]
// we need to consider both as they initialMeaning intersects with the module in the namespace space, and the module
// intersects with the class in the value space.
// To achieve that we will keep iterating until the result stabilizes.
// Remember the last meaning
lastIterationMeaning = meaning;
for (const declaration of declarations) {
const declarationMeaning = getMeaningFromDeclaration(declaration);
if (declarationMeaning & meaning) {
meaning |= declarationMeaning;
}
}
}
while (meaning !== lastIterationMeaning);
}
return meaning;
}
}
function getReferenceEntryFromNode(node: Node): ReferenceEntry {
let start = node.getStart();
let end = node.getEnd();
if (node.kind === SyntaxKind.StringLiteral) {
start += 1;
end -= 1;
}
return {
fileName: node.getSourceFile().fileName,
textSpan: createTextSpanFromBounds(start, end),
isWriteAccess: isWriteAccess(node)
};
}
/** A node is considered a writeAccess iff it is a name of a declaration or a target of an assignment */
function isWriteAccess(node: Node): boolean {
if (node.kind === SyntaxKind.Identifier && isDeclarationName(node)) {
return true;
}
const parent = node.parent;
if (parent) {
if (parent.kind === SyntaxKind.PostfixUnaryExpression || parent.kind === SyntaxKind.PrefixUnaryExpression) {
return true;
}
else if (parent.kind === SyntaxKind.BinaryExpression && (parent).left === node) {
const operator = (parent).operatorToken.kind;
return SyntaxKind.FirstAssignment <= operator && operator <= SyntaxKind.LastAssignment;
}
}
return false;
}
/// NavigateTo
function getNavigateToItems(searchValue: string, maxResultCount?: number): NavigateToItem[] {
synchronizeHostData();
const checker = getProgram().getTypeChecker();
return ts.NavigateTo.getNavigateToItems(program, checker, cancellationToken, searchValue, maxResultCount);
}
function getEmitOutput(fileName: string): EmitOutput {
synchronizeHostData();
const sourceFile = getValidSourceFile(fileName);
const outputFiles: OutputFile[] = [];
function writeFile(fileName: string, data: string, writeByteOrderMark: boolean) {
outputFiles.push({
name: fileName,
writeByteOrderMark: writeByteOrderMark,
text: data
});
}
const emitOutput = program.emit(sourceFile, writeFile, cancellationToken);
return {
outputFiles,
emitSkipped: emitOutput.emitSkipped
};
}
function getMeaningFromDeclaration(node: Node): SemanticMeaning {
switch (node.kind) {
case SyntaxKind.Parameter:
case SyntaxKind.VariableDeclaration:
case SyntaxKind.BindingElement:
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.PropertySignature:
case SyntaxKind.PropertyAssignment:
case SyntaxKind.ShorthandPropertyAssignment:
case SyntaxKind.EnumMember:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.MethodSignature:
case SyntaxKind.Constructor:
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.FunctionExpression:
case SyntaxKind.ArrowFunction:
case SyntaxKind.CatchClause:
return SemanticMeaning.Value;
case SyntaxKind.TypeParameter:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.TypeAliasDeclaration:
case SyntaxKind.TypeLiteral:
return SemanticMeaning.Type;
case SyntaxKind.ClassDeclaration:
case SyntaxKind.EnumDeclaration:
return SemanticMeaning.Value | SemanticMeaning.Type;
case SyntaxKind.ModuleDeclaration:
if (isAmbientModule(node)) {
return SemanticMeaning.Namespace | SemanticMeaning.Value;
}
else if (getModuleInstanceState(node) === ModuleInstanceState.Instantiated) {
return SemanticMeaning.Namespace | SemanticMeaning.Value;
}
else {
return SemanticMeaning.Namespace;
}
case SyntaxKind.NamedImports:
case SyntaxKind.ImportSpecifier:
case SyntaxKind.ImportEqualsDeclaration:
case SyntaxKind.ImportDeclaration:
case SyntaxKind.ExportAssignment:
case SyntaxKind.ExportDeclaration:
return SemanticMeaning.Value | SemanticMeaning.Type | SemanticMeaning.Namespace;
// An external module can be a Value
case SyntaxKind.SourceFile:
return SemanticMeaning.Namespace | SemanticMeaning.Value;
}
return SemanticMeaning.Value | SemanticMeaning.Type | SemanticMeaning.Namespace;
}
function isTypeReference(node: Node): boolean {
if (isRightSideOfQualifiedNameOrPropertyAccess(node)) {
node = node.parent;
}
return node.parent.kind === SyntaxKind.TypeReference ||
(node.parent.kind === SyntaxKind.ExpressionWithTypeArguments && !isExpressionWithTypeArgumentsInClassExtendsClause(node.parent)) ||
(node.kind === SyntaxKind.ThisKeyword && !isExpression(node)) ||
node.kind === SyntaxKind.ThisType;
}
function isNamespaceReference(node: Node): boolean {
return isQualifiedNameNamespaceReference(node) || isPropertyAccessNamespaceReference(node);
}
function isPropertyAccessNamespaceReference(node: Node): boolean {
let root = node;
let isLastClause = true;
if (root.parent.kind === SyntaxKind.PropertyAccessExpression) {
while (root.parent && root.parent.kind === SyntaxKind.PropertyAccessExpression) {
root = root.parent;
}
isLastClause = (root).name === node;
}
if (!isLastClause && root.parent.kind === SyntaxKind.ExpressionWithTypeArguments && root.parent.parent.kind === SyntaxKind.HeritageClause) {
const decl = root.parent.parent.parent;
return (decl.kind === SyntaxKind.ClassDeclaration && (root.parent.parent).token === SyntaxKind.ImplementsKeyword) ||
(decl.kind === SyntaxKind.InterfaceDeclaration && (root.parent.parent).token === SyntaxKind.ExtendsKeyword);
}
return false;
}
function isQualifiedNameNamespaceReference(node: Node): boolean {
let root = node;
let isLastClause = true;
if (root.parent.kind === SyntaxKind.QualifiedName) {
while (root.parent && root.parent.kind === SyntaxKind.QualifiedName) {
root = root.parent;
}
isLastClause = (root).right === node;
}
return root.parent.kind === SyntaxKind.TypeReference && !isLastClause;
}
function isInRightSideOfImport(node: Node) {
while (node.parent.kind === SyntaxKind.QualifiedName) {
node = node.parent;
}
return isInternalModuleImportEqualsDeclaration(node.parent) && (node.parent).moduleReference === node;
}
function getMeaningFromRightHandSideOfImportEquals(node: Node) {
Debug.assert(node.kind === SyntaxKind.Identifier);
// import a = |b|; // Namespace
// import a = |b.c|; // Value, type, namespace
// import a = |b.c|.d; // Namespace
if (node.parent.kind === SyntaxKind.QualifiedName &&
(node.parent).right === node &&
node.parent.parent.kind === SyntaxKind.ImportEqualsDeclaration) {
return SemanticMeaning.Value | SemanticMeaning.Type | SemanticMeaning.Namespace;
}
return SemanticMeaning.Namespace;
}
function getMeaningFromLocation(node: Node): SemanticMeaning {
if (node.parent.kind === SyntaxKind.ExportAssignment) {
return SemanticMeaning.Value | SemanticMeaning.Type | SemanticMeaning.Namespace;
}
else if (isInRightSideOfImport(node)) {
return getMeaningFromRightHandSideOfImportEquals(node);
}
else if (isDeclarationName(node)) {
return getMeaningFromDeclaration(node.parent);
}
else if (isTypeReference(node)) {
return SemanticMeaning.Type;
}
else if (isNamespaceReference(node)) {
return SemanticMeaning.Namespace;
}
else {
return SemanticMeaning.Value;
}
}
// Signature help
/**
* This is a semantic operation.
*/
function getSignatureHelpItems(fileName: string, position: number): SignatureHelpItems {
synchronizeHostData();
const sourceFile = getValidSourceFile(fileName);
return SignatureHelp.getSignatureHelpItems(program, sourceFile, position, cancellationToken);
}
/// Syntactic features
function getNonBoundSourceFile(fileName: string): SourceFile {
return syntaxTreeCache.getCurrentSourceFile(fileName);
}
function getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): TextSpan {
const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
// Get node at the location
const node = getTouchingPropertyName(sourceFile, startPos);
if (node === sourceFile) {
return;
}
switch (node.kind) {
case SyntaxKind.PropertyAccessExpression:
case SyntaxKind.QualifiedName:
case SyntaxKind.StringLiteral:
case SyntaxKind.StringLiteralType:
case SyntaxKind.FalseKeyword:
case SyntaxKind.TrueKeyword:
case SyntaxKind.NullKeyword:
case SyntaxKind.SuperKeyword:
case SyntaxKind.ThisKeyword:
case SyntaxKind.ThisType:
case SyntaxKind.Identifier:
break;
// Cant create the text span
default:
return;
}
let nodeForStartPos = node;
while (true) {
if (isRightSideOfPropertyAccess(nodeForStartPos) || isRightSideOfQualifiedName(nodeForStartPos)) {
// If on the span is in right side of the the property or qualified name, return the span from the qualified name pos to end of this node
nodeForStartPos = nodeForStartPos.parent;
}
else if (isNameOfModuleDeclaration(nodeForStartPos)) {
// If this is name of a module declarations, check if this is right side of dotted module name
// If parent of the module declaration which is parent of this node is module declaration and its body is the module declaration that this node is name of
// Then this name is name from dotted module
if (nodeForStartPos.parent.parent.kind === SyntaxKind.ModuleDeclaration &&
(nodeForStartPos.parent.parent).body === nodeForStartPos.parent) {
// Use parent module declarations name for start pos
nodeForStartPos = (nodeForStartPos.parent.parent).name;
}
else {
// We have to use this name for start pos
break;
}
}
else {
// Is not a member expression so we have found the node for start pos
break;
}
}
return createTextSpanFromBounds(nodeForStartPos.getStart(), node.getEnd());
}
function getBreakpointStatementAtPosition(fileName: string, position: number) {
// doesn't use compiler - no need to synchronize with host
const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
return BreakpointResolver.spanInSourceFileAtLocation(sourceFile, position);
}
function getNavigationBarItems(fileName: string): NavigationBarItem[] {
const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
return NavigationBar.getNavigationBarItems(sourceFile, host.getCompilationSettings());
}
function getSemanticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[] {
return convertClassifications(getEncodedSemanticClassifications(fileName, span));
}
function checkForClassificationCancellation(kind: SyntaxKind) {
// We don't want to actually call back into our host on every node to find out if we've
// been canceled. That would be an enormous amount of chattyness, along with the all
// the overhead of marshalling the data to/from the host. So instead we pick a few
// reasonable node kinds to bother checking on. These node kinds represent high level
// constructs that we would expect to see commonly, but just at a far less frequent
// interval.
//
// For example, in checker.ts (around 750k) we only have around 600 of these constructs.
// That means we're calling back into the host around every 1.2k of the file we process.
// Lib.d.ts has similar numbers.
switch (kind) {
case SyntaxKind.ModuleDeclaration:
case SyntaxKind.ClassDeclaration:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.FunctionDeclaration:
cancellationToken.throwIfCancellationRequested();
}
}
function getEncodedSemanticClassifications(fileName: string, span: TextSpan): Classifications {
synchronizeHostData();
const sourceFile = getValidSourceFile(fileName);
const typeChecker = program.getTypeChecker();
const result: number[] = [];
const classifiableNames = program.getClassifiableNames();
processNode(sourceFile);
return { spans: result, endOfLineState: EndOfLineState.None };
function pushClassification(start: number, length: number, type: ClassificationType) {
result.push(start);
result.push(length);
result.push(type);
}
function classifySymbol(symbol: Symbol, meaningAtPosition: SemanticMeaning): ClassificationType {
const flags = symbol.getFlags();
if ((flags & SymbolFlags.Classifiable) === SymbolFlags.None) {
return;
}
if (flags & SymbolFlags.Class) {
return ClassificationType.className;
}
else if (flags & SymbolFlags.Enum) {
return ClassificationType.enumName;
}
else if (flags & SymbolFlags.TypeAlias) {
return ClassificationType.typeAliasName;
}
else if (meaningAtPosition & SemanticMeaning.Type) {
if (flags & SymbolFlags.Interface) {
return ClassificationType.interfaceName;
}
else if (flags & SymbolFlags.TypeParameter) {
return ClassificationType.typeParameterName;
}
}
else if (flags & SymbolFlags.Module) {
// Only classify a module as such if
// - It appears in a namespace context.
// - There exists a module declaration which actually impacts the value side.
if (meaningAtPosition & SemanticMeaning.Namespace ||
(meaningAtPosition & SemanticMeaning.Value && hasValueSideModule(symbol))) {
return ClassificationType.moduleName;
}
}
return undefined;
/**
* Returns true if there exists a module that introduces entities on the value side.
*/
function hasValueSideModule(symbol: Symbol): boolean {
return forEach(symbol.declarations, declaration => {
return declaration.kind === SyntaxKind.ModuleDeclaration &&
getModuleInstanceState(declaration) === ModuleInstanceState.Instantiated;
});
}
}
function processNode(node: Node) {
// Only walk into nodes that intersect the requested span.
if (node && textSpanIntersectsWith(span, node.getFullStart(), node.getFullWidth())) {
const kind = node.kind;
checkForClassificationCancellation(kind);
if (kind === SyntaxKind.Identifier && !nodeIsMissing(node)) {
const identifier = node;
// Only bother calling into the typechecker if this is an identifier that
// could possibly resolve to a type name. This makes classification run
// in a third of the time it would normally take.
if (classifiableNames[identifier.text]) {
const symbol = typeChecker.getSymbolAtLocation(node);
if (symbol) {
const type = classifySymbol(symbol, getMeaningFromLocation(node));
if (type) {
pushClassification(node.getStart(), node.getWidth(), type);
}
}
}
}
forEachChild(node, processNode);
}
}
}
function getClassificationTypeName(type: ClassificationType) {
switch (type) {
case ClassificationType.comment: return ClassificationTypeNames.comment;
case ClassificationType.identifier: return ClassificationTypeNames.identifier;
case ClassificationType.keyword: return ClassificationTypeNames.keyword;
case ClassificationType.numericLiteral: return ClassificationTypeNames.numericLiteral;
case ClassificationType.operator: return ClassificationTypeNames.operator;
case ClassificationType.stringLiteral: return ClassificationTypeNames.stringLiteral;
case ClassificationType.whiteSpace: return ClassificationTypeNames.whiteSpace;
case ClassificationType.text: return ClassificationTypeNames.text;
case ClassificationType.punctuation: return ClassificationTypeNames.punctuation;
case ClassificationType.className: return ClassificationTypeNames.className;
case ClassificationType.enumName: return ClassificationTypeNames.enumName;
case ClassificationType.interfaceName: return ClassificationTypeNames.interfaceName;
case ClassificationType.moduleName: return ClassificationTypeNames.moduleName;
case ClassificationType.typeParameterName: return ClassificationTypeNames.typeParameterName;
case ClassificationType.typeAliasName: return ClassificationTypeNames.typeAliasName;
case ClassificationType.parameterName: return ClassificationTypeNames.parameterName;
case ClassificationType.docCommentTagName: return ClassificationTypeNames.docCommentTagName;
case ClassificationType.jsxOpenTagName: return ClassificationTypeNames.jsxOpenTagName;
case ClassificationType.jsxCloseTagName: return ClassificationTypeNames.jsxCloseTagName;
case ClassificationType.jsxSelfClosingTagName: return ClassificationTypeNames.jsxSelfClosingTagName;
case ClassificationType.jsxAttribute: return ClassificationTypeNames.jsxAttribute;
case ClassificationType.jsxText: return ClassificationTypeNames.jsxText;
case ClassificationType.jsxAttributeStringLiteralValue: return ClassificationTypeNames.jsxAttributeStringLiteralValue;
}
}
function convertClassifications(classifications: Classifications): ClassifiedSpan[] {
Debug.assert(classifications.spans.length % 3 === 0);
const dense = classifications.spans;
const result: ClassifiedSpan[] = [];
for (let i = 0, n = dense.length; i < n; i += 3) {
result.push({
textSpan: createTextSpan(dense[i], dense[i + 1]),
classificationType: getClassificationTypeName(dense[i + 2])
});
}
return result;
}
function getSyntacticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[] {
return convertClassifications(getEncodedSyntacticClassifications(fileName, span));
}
function getEncodedSyntacticClassifications(fileName: string, span: TextSpan): Classifications {
// doesn't use compiler - no need to synchronize with host
const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
const spanStart = span.start;
const spanLength = span.length;
// Make a scanner we can get trivia from.
const triviaScanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ false, sourceFile.languageVariant, sourceFile.text);
const mergeConflictScanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ false, sourceFile.languageVariant, sourceFile.text);
const result: number[] = [];
processElement(sourceFile);
return { spans: result, endOfLineState: EndOfLineState.None };
function pushClassification(start: number, length: number, type: ClassificationType) {
result.push(start);
result.push(length);
result.push(type);
}
function classifyLeadingTriviaAndGetTokenStart(token: Node): number {
triviaScanner.setTextPos(token.pos);
while (true) {
const start = triviaScanner.getTextPos();
// only bother scanning if we have something that could be trivia.
if (!couldStartTrivia(sourceFile.text, start)) {
return start;
}
const kind = triviaScanner.scan();
const end = triviaScanner.getTextPos();
const width = end - start;
// The moment we get something that isn't trivia, then stop processing.
if (!isTrivia(kind)) {
return start;
}
// Don't bother with newlines/whitespace.
if (kind === SyntaxKind.NewLineTrivia || kind === SyntaxKind.WhitespaceTrivia) {
continue;
}
// Only bother with the trivia if it at least intersects the span of interest.
if (isComment(kind)) {
classifyComment(token, kind, start, width);
// Classifying a comment might cause us to reuse the trivia scanner
// (because of jsdoc comments). So after we classify the comment make
// sure we set the scanner position back to where it needs to be.
triviaScanner.setTextPos(end);
continue;
}
if (kind === SyntaxKind.ConflictMarkerTrivia) {
const text = sourceFile.text;
const ch = text.charCodeAt(start);
// for the <<<<<<< and >>>>>>> markers, we just add them in as comments
// in the classification stream.
if (ch === CharacterCodes.lessThan || ch === CharacterCodes.greaterThan) {
pushClassification(start, width, ClassificationType.comment);
continue;
}
// for the ======== add a comment for the first line, and then lex all
// subsequent lines up until the end of the conflict marker.
Debug.assert(ch === CharacterCodes.equals);
classifyDisabledMergeCode(text, start, end);
}
}
}
function classifyComment(token: Node, kind: SyntaxKind, start: number, width: number) {
if (kind === SyntaxKind.MultiLineCommentTrivia) {
// See if this is a doc comment. If so, we'll classify certain portions of it
// specially.
const docCommentAndDiagnostics = parseIsolatedJSDocComment(sourceFile.text, start, width);
if (docCommentAndDiagnostics && docCommentAndDiagnostics.jsDocComment) {
docCommentAndDiagnostics.jsDocComment.parent = token;
classifyJSDocComment(docCommentAndDiagnostics.jsDocComment);
return;
}
}
// Simple comment. Just add as is.
pushCommentRange(start, width);
}
function pushCommentRange(start: number, width: number) {
pushClassification(start, width, ClassificationType.comment);
}
function classifyJSDocComment(docComment: JSDocComment) {
let pos = docComment.pos;
for (const tag of docComment.tags) {
// As we walk through each tag, classify the portion of text from the end of
// the last tag (or the start of the entire doc comment) as 'comment'.
if (tag.pos !== pos) {
pushCommentRange(pos, tag.pos - pos);
}
pushClassification(tag.atToken.pos, tag.atToken.end - tag.atToken.pos, ClassificationType.punctuation);
pushClassification(tag.tagName.pos, tag.tagName.end - tag.tagName.pos, ClassificationType.docCommentTagName);
pos = tag.tagName.end;
switch (tag.kind) {
case SyntaxKind.JSDocParameterTag:
processJSDocParameterTag(tag);
break;
case SyntaxKind.JSDocTemplateTag:
processJSDocTemplateTag(tag);
break;
case SyntaxKind.JSDocTypeTag:
processElement((tag).typeExpression);
break;
case SyntaxKind.JSDocReturnTag:
processElement((tag).typeExpression);
break;
}
pos = tag.end;
}
if (pos !== docComment.end) {
pushCommentRange(pos, docComment.end - pos);
}
return;
function processJSDocParameterTag(tag: JSDocParameterTag) {
if (tag.preParameterName) {
pushCommentRange(pos, tag.preParameterName.pos - pos);
pushClassification(tag.preParameterName.pos, tag.preParameterName.end - tag.preParameterName.pos, ClassificationType.parameterName);
pos = tag.preParameterName.end;
}
if (tag.typeExpression) {
pushCommentRange(pos, tag.typeExpression.pos - pos);
processElement(tag.typeExpression);
pos = tag.typeExpression.end;
}
if (tag.postParameterName) {
pushCommentRange(pos, tag.postParameterName.pos - pos);
pushClassification(tag.postParameterName.pos, tag.postParameterName.end - tag.postParameterName.pos, ClassificationType.parameterName);
pos = tag.postParameterName.end;
}
}
}
function processJSDocTemplateTag(tag: JSDocTemplateTag) {
for (const child of tag.getChildren()) {
processElement(child);
}
}
function classifyDisabledMergeCode(text: string, start: number, end: number) {
// Classify the line that the ======= marker is on as a comment. Then just lex
// all further tokens and add them to the result.
let i: number;
for (i = start; i < end; i++) {
if (isLineBreak(text.charCodeAt(i))) {
break;
}
}
pushClassification(start, i - start, ClassificationType.comment);
mergeConflictScanner.setTextPos(i);
while (mergeConflictScanner.getTextPos() < end) {
classifyDisabledCodeToken();
}
}
function classifyDisabledCodeToken() {
const start = mergeConflictScanner.getTextPos();
const tokenKind = mergeConflictScanner.scan();
const end = mergeConflictScanner.getTextPos();
const type = classifyTokenType(tokenKind);
if (type) {
pushClassification(start, end - start, type);
}
}
/**
* Returns true if node should be treated as classified and no further processing is required.
* False will mean that node is not classified and traverse routine should recurse into node contents.
*/
function tryClassifyNode(node: Node): boolean {
if (nodeIsMissing(node)) {
return true;
}
const classifiedElementName = tryClassifyJsxElementName(node);
if (!isToken(node) && node.kind !== SyntaxKind.JsxText && classifiedElementName === undefined) {
return false;
}
const tokenStart = node.kind === SyntaxKind.JsxText ? node.pos : classifyLeadingTriviaAndGetTokenStart(node);
const tokenWidth = node.end - tokenStart;
Debug.assert(tokenWidth >= 0);
if (tokenWidth > 0) {
const type = classifiedElementName || classifyTokenType(node.kind, node);
if (type) {
pushClassification(tokenStart, tokenWidth, type);
}
}
return true;
}
function tryClassifyJsxElementName(token: Node): ClassificationType {
switch (token.parent && token.parent.kind) {
case SyntaxKind.JsxOpeningElement:
if ((token.parent).tagName === token) {
return ClassificationType.jsxOpenTagName;
}
break;
case SyntaxKind.JsxClosingElement:
if ((token.parent).tagName === token) {
return ClassificationType.jsxCloseTagName;
}
break;
case SyntaxKind.JsxSelfClosingElement:
if ((token.parent).tagName === token) {
return ClassificationType.jsxSelfClosingTagName;
}
break;
case SyntaxKind.JsxAttribute:
if ((token.parent).name === token) {
return ClassificationType.jsxAttribute;
}
break;
}
return undefined;
}
// for accurate classification, the actual token should be passed in. however, for
// cases like 'disabled merge code' classification, we just get the token kind and
// classify based on that instead.
function classifyTokenType(tokenKind: SyntaxKind, token?: Node): ClassificationType {
if (isKeyword(tokenKind)) {
return ClassificationType.keyword;
}
// Special case < and > If they appear in a generic context they are punctuation,
// not operators.
if (tokenKind === SyntaxKind.LessThanToken || tokenKind === SyntaxKind.GreaterThanToken) {
// If the node owning the token has a type argument list or type parameter list, then
// we can effectively assume that a '<' and '>' belong to those lists.
if (token && getTypeArgumentOrTypeParameterList(token.parent)) {
return ClassificationType.punctuation;
}
}
if (isPunctuation(tokenKind)) {
if (token) {
if (tokenKind === SyntaxKind.EqualsToken) {
// the '=' in a variable declaration is special cased here.
if (token.parent.kind === SyntaxKind.VariableDeclaration ||
token.parent.kind === SyntaxKind.PropertyDeclaration ||
token.parent.kind === SyntaxKind.Parameter ||
token.parent.kind === SyntaxKind.JsxAttribute) {
return ClassificationType.operator;
}
}
if (token.parent.kind === SyntaxKind.BinaryExpression ||
token.parent.kind === SyntaxKind.PrefixUnaryExpression ||
token.parent.kind === SyntaxKind.PostfixUnaryExpression ||
token.parent.kind === SyntaxKind.ConditionalExpression) {
return ClassificationType.operator;
}
}
return ClassificationType.punctuation;
}
else if (tokenKind === SyntaxKind.NumericLiteral) {
return ClassificationType.numericLiteral;
}
else if (tokenKind === SyntaxKind.StringLiteral || tokenKind === SyntaxKind.StringLiteralType) {
return token.parent.kind === SyntaxKind.JsxAttribute ? ClassificationType.jsxAttributeStringLiteralValue : ClassificationType.stringLiteral;
}
else if (tokenKind === SyntaxKind.RegularExpressionLiteral) {
// TODO: we should get another classification type for these literals.
return ClassificationType.stringLiteral;
}
else if (isTemplateLiteralKind(tokenKind)) {
// TODO (drosen): we should *also* get another classification type for these literals.
return ClassificationType.stringLiteral;
}
else if (tokenKind === SyntaxKind.JsxText) {
return ClassificationType.jsxText;
}
else if (tokenKind === SyntaxKind.Identifier) {
if (token) {
switch (token.parent.kind) {
case SyntaxKind.ClassDeclaration:
if ((token.parent).name === token) {
return ClassificationType.className;
}
return;
case SyntaxKind.TypeParameter:
if ((token.parent).name === token) {
return ClassificationType.typeParameterName;
}
return;
case SyntaxKind.InterfaceDeclaration:
if ((token.parent).name === token) {
return ClassificationType.interfaceName;
}
return;
case SyntaxKind.EnumDeclaration:
if ((token.parent).name === token) {
return ClassificationType.enumName;
}
return;
case SyntaxKind.ModuleDeclaration:
if ((token.parent).name === token) {
return ClassificationType.moduleName;
}
return;
case SyntaxKind.Parameter:
if ((token.parent).name === token) {
return ClassificationType.parameterName;
}
return;
}
}
return ClassificationType.identifier;
}
}
function processElement(element: Node) {
if (!element) {
return;
}
// Ignore nodes that don't intersect the original span to classify.
if (decodedTextSpanIntersectsWith(spanStart, spanLength, element.pos, element.getFullWidth())) {
checkForClassificationCancellation(element.kind);
const children = element.getChildren(sourceFile);
for (let i = 0, n = children.length; i < n; i++) {
const child = children[i];
if (!tryClassifyNode(child)) {
// Recurse into our child nodes.
processElement(child);
}
}
}
}
}
function getOutliningSpans(fileName: string): OutliningSpan[] {
// doesn't use compiler - no need to synchronize with host
const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
return OutliningElementsCollector.collectElements(sourceFile);
}
function getBraceMatchingAtPosition(fileName: string, position: number) {
const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
const result: TextSpan[] = [];
const token = getTouchingToken(sourceFile, position);
if (token.getStart(sourceFile) === position) {
const matchKind = getMatchingTokenKind(token);
// Ensure that there is a corresponding token to match ours.
if (matchKind) {
const parentElement = token.parent;
const childNodes = parentElement.getChildren(sourceFile);
for (const current of childNodes) {
if (current.kind === matchKind) {
const range1 = createTextSpan(token.getStart(sourceFile), token.getWidth(sourceFile));
const range2 = createTextSpan(current.getStart(sourceFile), current.getWidth(sourceFile));
// We want to order the braces when we return the result.
if (range1.start < range2.start) {
result.push(range1, range2);
}
else {
result.push(range2, range1);
}
break;
}
}
}
}
return result;
function getMatchingTokenKind(token: Node): ts.SyntaxKind {
switch (token.kind) {
case ts.SyntaxKind.OpenBraceToken: return ts.SyntaxKind.CloseBraceToken;
case ts.SyntaxKind.OpenParenToken: return ts.SyntaxKind.CloseParenToken;
case ts.SyntaxKind.OpenBracketToken: return ts.SyntaxKind.CloseBracketToken;
case ts.SyntaxKind.LessThanToken: return ts.SyntaxKind.GreaterThanToken;
case ts.SyntaxKind.CloseBraceToken: return ts.SyntaxKind.OpenBraceToken;
case ts.SyntaxKind.CloseParenToken: return ts.SyntaxKind.OpenParenToken;
case ts.SyntaxKind.CloseBracketToken: return ts.SyntaxKind.OpenBracketToken;
case ts.SyntaxKind.GreaterThanToken: return ts.SyntaxKind.LessThanToken;
}
return undefined;
}
}
function getIndentationAtPosition(fileName: string, position: number, editorOptions: EditorOptions) {
let start = new Date().getTime();
const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
log("getIndentationAtPosition: getCurrentSourceFile: " + (new Date().getTime() - start));
start = new Date().getTime();
const result = formatting.SmartIndenter.getIndentation(position, sourceFile, editorOptions);
log("getIndentationAtPosition: computeIndentation : " + (new Date().getTime() - start));
return result;
}
function getFormattingEditsForRange(fileName: string, start: number, end: number, options: FormatCodeOptions): TextChange[] {
const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
return formatting.formatSelection(start, end, sourceFile, getRuleProvider(options), options);
}
function getFormattingEditsForDocument(fileName: string, options: FormatCodeOptions): TextChange[] {
const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
return formatting.formatDocument(sourceFile, getRuleProvider(options), options);
}
function getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions): TextChange[] {
const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
if (key === "}") {
return formatting.formatOnClosingCurly(position, sourceFile, getRuleProvider(options), options);
}
else if (key === ";") {
return formatting.formatOnSemicolon(position, sourceFile, getRuleProvider(options), options);
}
else if (key === "\n") {
return formatting.formatOnEnter(position, sourceFile, getRuleProvider(options), options);
}
return [];
}
/**
* Checks if position points to a valid position to add JSDoc comments, and if so,
* returns the appropriate template. Otherwise returns an empty string.
* Valid positions are
* - outside of comments, statements, and expressions, and
* - preceding a:
* - function/constructor/method declaration
* - class declarations
* - variable statements
* - namespace declarations
*
* Hosts should ideally check that:
* - The line is all whitespace up to 'position' before performing the insertion.
* - If the keystroke sequence "/\*\*" induced the call, we also check that the next
* non-whitespace character is '*', which (approximately) indicates whether we added
* the second '*' to complete an existing (JSDoc) comment.
* @param fileName The file in which to perform the check.
* @param position The (character-indexed) position in the file where the check should
* be performed.
*/
function getDocCommentTemplateAtPosition(fileName: string, position: number): TextInsertion {
const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
// Check if in a context where we don't want to perform any insertion
if (isInString(sourceFile, position) || isInComment(sourceFile, position) || hasDocComment(sourceFile, position)) {
return undefined;
}
const tokenAtPos = getTokenAtPosition(sourceFile, position);
const tokenStart = tokenAtPos.getStart();
if (!tokenAtPos || tokenStart < position) {
return undefined;
}
// TODO: add support for:
// - enums/enum members
// - interfaces
// - property declarations
// - potentially property assignments
let commentOwner: Node;
findOwner: for (commentOwner = tokenAtPos; commentOwner; commentOwner = commentOwner.parent) {
switch (commentOwner.kind) {
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.Constructor:
case SyntaxKind.ClassDeclaration:
case SyntaxKind.VariableStatement:
break findOwner;
case SyntaxKind.SourceFile:
return undefined;
case SyntaxKind.ModuleDeclaration:
// If in walking up the tree, we hit a a nested namespace declaration,
// then we must be somewhere within a dotted namespace name; however we don't
// want to give back a JSDoc template for the 'b' or 'c' in 'namespace a.b.c { }'.
if (commentOwner.parent.kind === SyntaxKind.ModuleDeclaration) {
return undefined;
}
break findOwner;
}
}
if (!commentOwner || commentOwner.getStart() < position) {
return undefined;
}
const parameters = getParametersForJsDocOwningNode(commentOwner);
const posLineAndChar = sourceFile.getLineAndCharacterOfPosition(position);
const lineStart = sourceFile.getLineStarts()[posLineAndChar.line];
const indentationStr = sourceFile.text.substr(lineStart, posLineAndChar.character);
const newLine = getNewLineOrDefaultFromHost(host);
let docParams = "";
for (let i = 0, numParams = parameters.length; i < numParams; i++) {
const currentName = parameters[i].name;
const paramName = currentName.kind === SyntaxKind.Identifier ?
(currentName).text :
"param" + i;
docParams += `${indentationStr} * @param ${paramName}${newLine}`;
}
// A doc comment consists of the following
// * The opening comment line
// * the first line (without a param) for the object's untagged info (this is also where the caret ends up)
// * the '@param'-tagged lines
// * TODO: other tags.
// * the closing comment line
// * if the caret was directly in front of the object, then we add an extra line and indentation.
const preamble = "/**" + newLine +
indentationStr + " * ";
const result =
preamble + newLine +
docParams +
indentationStr + " */" +
(tokenStart === position ? newLine + indentationStr : "");
return { newText: result, caretOffset: preamble.length };
}
function isValidBraceCompletionAtPostion(fileName: string, position: number, openingBrace: number): boolean {
// '<' is currently not supported, figuring out if we're in a Generic Type vs. a comparison is too
// expensive to do during typing scenarios
// i.e. whether we're dealing with:
// var x = new foo<| ( with class foo{} )
// or
// var y = 3 <|
if (openingBrace === CharacterCodes.lessThan) {
return false;
}
const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
// Check if in a context where we don't want to perform any insertion
if (isInString(sourceFile, position) || isInComment(sourceFile, position)) {
return false;
}
if (isInsideJsxElementOrAttribute(sourceFile, position)) {
return openingBrace === CharacterCodes.openBrace;
}
if (isInTemplateString(sourceFile, position)) {
return false;
}
return true;
}
function getParametersForJsDocOwningNode(commentOwner: Node): ParameterDeclaration[] {
if (isFunctionLike(commentOwner)) {
return commentOwner.parameters;
}
if (commentOwner.kind === SyntaxKind.VariableStatement) {
const varStatement = commentOwner;
const varDeclarations = varStatement.declarationList.declarations;
if (varDeclarations.length === 1 && varDeclarations[0].initializer) {
return getParametersFromRightHandSideOfAssignment(varDeclarations[0].initializer);
}
}
return emptyArray;
}
/**
* Digs into an an initializer or RHS operand of an assignment operation
* to get the parameters of an apt signature corresponding to a
* function expression or a class expression.
*
* @param rightHandSide the expression which may contain an appropriate set of parameters
* @returns the parameters of a signature found on the RHS if one exists; otherwise 'emptyArray'.
*/
function getParametersFromRightHandSideOfAssignment(rightHandSide: Expression): ParameterDeclaration[] {
while (rightHandSide.kind === SyntaxKind.ParenthesizedExpression) {
rightHandSide = (rightHandSide).expression;
}
switch (rightHandSide.kind) {
case SyntaxKind.FunctionExpression:
case SyntaxKind.ArrowFunction:
return (rightHandSide).parameters;
case SyntaxKind.ClassExpression:
for (const member of (rightHandSide).members) {
if (member.kind === SyntaxKind.Constructor) {
return (member).parameters;
}
}
break;
}
return emptyArray;
}
function getTodoComments(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[] {
// Note: while getting todo comments seems like a syntactic operation, we actually
// treat it as a semantic operation here. This is because we expect our host to call
// this on every single file. If we treat this syntactically, then that will cause
// us to populate and throw away the tree in our syntax tree cache for each file. By
// treating this as a semantic operation, we can access any tree without throwing
// anything away.
synchronizeHostData();
const sourceFile = getValidSourceFile(fileName);
cancellationToken.throwIfCancellationRequested();
const fileContents = sourceFile.text;
const result: TodoComment[] = [];
if (descriptors.length > 0) {
const regExp = getTodoCommentsRegExp();
let matchArray: RegExpExecArray;
while (matchArray = regExp.exec(fileContents)) {
cancellationToken.throwIfCancellationRequested();
// If we got a match, here is what the match array will look like. Say the source text is:
//
// " // hack 1"
//
// The result array with the regexp: will be:
//
// ["// hack 1", "// ", "hack 1", undefined, "hack"]
//
// Here are the relevant capture groups:
// 0) The full match for the entire regexp.
// 1) The preamble to the message portion.
// 2) The message portion.
// 3...N) The descriptor that was matched - by index. 'undefined' for each
// descriptor that didn't match. an actual value if it did match.
//
// i.e. 'undefined' in position 3 above means TODO(jason) didn't match.
// "hack" in position 4 means HACK did match.
const firstDescriptorCaptureIndex = 3;
Debug.assert(matchArray.length === descriptors.length + firstDescriptorCaptureIndex);
const preamble = matchArray[1];
const matchPosition = matchArray.index + preamble.length;
// OK, we have found a match in the file. This is only an acceptable match if
// it is contained within a comment.
const token = getTokenAtPosition(sourceFile, matchPosition);
if (!isInsideComment(sourceFile, token, matchPosition)) {
continue;
}
let descriptor: TodoCommentDescriptor = undefined;
for (let i = 0, n = descriptors.length; i < n; i++) {
if (matchArray[i + firstDescriptorCaptureIndex]) {
descriptor = descriptors[i];
}
}
Debug.assert(descriptor !== undefined);
// We don't want to match something like 'TODOBY', so we make sure a non
// letter/digit follows the match.
if (isLetterOrDigit(fileContents.charCodeAt(matchPosition + descriptor.text.length))) {
continue;
}
const message = matchArray[2];
result.push({
descriptor: descriptor,
message: message,
position: matchPosition
});
}
}
return result;
function escapeRegExp(str: string): string {
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}
function getTodoCommentsRegExp(): RegExp {
// NOTE: ?: means 'non-capture group'. It allows us to have groups without having to
// filter them out later in the final result array.
// TODO comments can appear in one of the following forms:
//
// 1) // TODO or /////////// TODO
//
// 2) /* TODO or /********** TODO
//
// 3) /*
// * TODO
// */
//
// The following three regexps are used to match the start of the text up to the TODO
// comment portion.
const singleLineCommentStart = /(?:\/\/+\s*)/.source;
const multiLineCommentStart = /(?:\/\*+\s*)/.source;
const anyNumberOfSpacesAndAsterisksAtStartOfLine = /(?:^(?:\s|\*)*)/.source;
// Match any of the above three TODO comment start regexps.
// Note that the outermost group *is* a capture group. We want to capture the preamble
// so that we can determine the starting position of the TODO comment match.
const preamble = "(" + anyNumberOfSpacesAndAsterisksAtStartOfLine + "|" + singleLineCommentStart + "|" + multiLineCommentStart + ")";
// Takes the descriptors and forms a regexp that matches them as if they were literals.
// For example, if the descriptors are "TODO(jason)" and "HACK", then this will be:
//
// (?:(TODO\(jason\))|(HACK))
//
// Note that the outermost group is *not* a capture group, but the innermost groups
// *are* capture groups. By capturing the inner literals we can determine after
// matching which descriptor we are dealing with.
const literals = "(?:" + map(descriptors, d => "(" + escapeRegExp(d.text) + ")").join("|") + ")";
// After matching a descriptor literal, the following regexp matches the rest of the
// text up to the end of the line (or */).
const endOfLineOrEndOfComment = /(?:$|\*\/)/.source;
const messageRemainder = /(?:.*?)/.source;
// This is the portion of the match we'll return as part of the TODO comment result. We
// match the literal portion up to the end of the line or end of comment.
const messagePortion = "(" + literals + messageRemainder + ")";
const regExpString = preamble + messagePortion + endOfLineOrEndOfComment;
// The final regexp will look like this:
// /((?:\/\/+\s*)|(?:\/\*+\s*)|(?:^(?:\s|\*)*))((?:(TODO\(jason\))|(HACK))(?:.*?))(?:$|\*\/)/gim
// The flags of the regexp are important here.
// 'g' is so that we are doing a global search and can find matches several times
// in the input.
//
// 'i' is for case insensitivity (We do this to match C# TODO comment code).
//
// 'm' is so we can find matches in a multi-line input.
return new RegExp(regExpString, "gim");
}
function isLetterOrDigit(char: number): boolean {
return (char >= CharacterCodes.a && char <= CharacterCodes.z) ||
(char >= CharacterCodes.A && char <= CharacterCodes.Z) ||
(char >= CharacterCodes._0 && char <= CharacterCodes._9);
}
}
function getStringLiteralTypeForNode(node: StringLiteral | StringLiteralTypeNode, typeChecker: TypeChecker): StringLiteralType {
const searchNode = node.parent.kind === SyntaxKind.StringLiteralType ? node.parent : node;
const type = typeChecker.getTypeAtLocation(searchNode);
if (type && type.flags & TypeFlags.StringLiteral) {
return type;
}
return undefined;
}
function getRenameInfo(fileName: string, position: number): RenameInfo {
synchronizeHostData();
const sourceFile = getValidSourceFile(fileName);
const typeChecker = program.getTypeChecker();
const defaultLibFileName = host.getDefaultLibFileName(host.getCompilationSettings());
const canonicalDefaultLibName = getCanonicalFileName(ts.normalizePath(defaultLibFileName));
const node = getTouchingWord(sourceFile, position, /*includeJsDocComment*/ true);
// Can only rename an identifier.
if (node) {
if (node.kind === SyntaxKind.Identifier ||
node.kind === SyntaxKind.StringLiteral ||
isLiteralNameOfPropertyDeclarationOrIndexAccess(node)) {
const symbol = typeChecker.getSymbolAtLocation(node);
// Only allow a symbol to be renamed if it actually has at least one declaration.
if (symbol) {
const declarations = symbol.getDeclarations();
if (declarations && declarations.length > 0) {
// Disallow rename for elements that are defined in the standard TypeScript library.
if (forEach(declarations, isDefinedInLibraryFile)) {
return getRenameInfoError(getLocaleSpecificMessage(Diagnostics.You_cannot_rename_elements_that_are_defined_in_the_standard_TypeScript_library));
}
const displayName = stripQuotes(getDeclaredName(typeChecker, symbol, node));
const kind = getSymbolKind(symbol, node);
if (kind) {
return {
canRename: true,
kind,
displayName,
localizedErrorMessage: undefined,
fullDisplayName: typeChecker.getFullyQualifiedName(symbol),
kindModifiers: getSymbolModifiers(symbol),
triggerSpan: createTriggerSpanForNode(node, sourceFile)
};
}
}
}
else if (node.kind === SyntaxKind.StringLiteral) {
const type = getStringLiteralTypeForNode(node, typeChecker);
if (type) {
if (isDefinedInLibraryFile(node)) {
return getRenameInfoError(getLocaleSpecificMessage(Diagnostics.You_cannot_rename_elements_that_are_defined_in_the_standard_TypeScript_library));
}
else {
const displayName = stripQuotes(type.text);
return {
canRename: true,
kind: ScriptElementKind.variableElement,
displayName,
localizedErrorMessage: undefined,
fullDisplayName: displayName,
kindModifiers: ScriptElementKindModifier.none,
triggerSpan: createTriggerSpanForNode(node, sourceFile)
};
}
}
}
}
}
return getRenameInfoError(getLocaleSpecificMessage(Diagnostics.You_cannot_rename_this_element));
function getRenameInfoError(localizedErrorMessage: string): RenameInfo {
return {
canRename: false,
localizedErrorMessage: localizedErrorMessage,
displayName: undefined,
fullDisplayName: undefined,
kind: undefined,
kindModifiers: undefined,
triggerSpan: undefined
};
}
function isDefinedInLibraryFile(declaration: Node) {
if (defaultLibFileName) {
const sourceFile = declaration.getSourceFile();
const canonicalName = getCanonicalFileName(ts.normalizePath(sourceFile.fileName));
if (canonicalName === canonicalDefaultLibName) {
return true;
}
}
return false;
}
function createTriggerSpanForNode(node: Node, sourceFile: SourceFile) {
let start = node.getStart(sourceFile);
let width = node.getWidth(sourceFile);
if (node.kind === SyntaxKind.StringLiteral) {
// Exclude the quotes
start += 1;
width -= 2;
}
return createTextSpan(start, width);
}
}
return {
dispose,
cleanupSemanticCache,
getSyntacticDiagnostics,
getSemanticDiagnostics,
getCompilerOptionsDiagnostics,
getSyntacticClassifications,
getSemanticClassifications,
getEncodedSyntacticClassifications,
getEncodedSemanticClassifications,
getCompletionsAtPosition,
getCompletionEntryDetails,
getSignatureHelpItems,
getQuickInfoAtPosition,
getDefinitionAtPosition,
getTypeDefinitionAtPosition,
getReferencesAtPosition,
findReferences,
getOccurrencesAtPosition,
getDocumentHighlights,
getNameOrDottedNameSpan,
getBreakpointStatementAtPosition,
getNavigateToItems,
getRenameInfo,
findRenameLocations,
getNavigationBarItems,
getOutliningSpans,
getTodoComments,
getBraceMatchingAtPosition,
getIndentationAtPosition,
getFormattingEditsForRange,
getFormattingEditsForDocument,
getFormattingEditsAfterKeystroke,
getDocCommentTemplateAtPosition,
isValidBraceCompletionAtPostion,
getEmitOutput,
getNonBoundSourceFile,
getProgram
};
}
/* @internal */
export function getNameTable(sourceFile: SourceFile): Map {
if (!sourceFile.nameTable) {
initializeNameTable(sourceFile);
}
return sourceFile.nameTable;
}
function initializeNameTable(sourceFile: SourceFile): void {
const nameTable: Map = {};
walk(sourceFile);
sourceFile.nameTable = nameTable;
function walk(node: Node) {
switch (node.kind) {
case SyntaxKind.Identifier:
nameTable[(node).text] = nameTable[(node).text] === undefined ? node.pos : -1;
break;
case SyntaxKind.StringLiteral:
case SyntaxKind.NumericLiteral:
// We want to store any numbers/strings if they were a name that could be
// related to a declaration. So, if we have 'import x = require("something")'
// then we want 'something' to be in the name table. Similarly, if we have
// "a['propname']" then we want to store "propname" in the name table.
if (isDeclarationName(node) ||
node.parent.kind === SyntaxKind.ExternalModuleReference ||
isArgumentOfElementAccessExpression(node) ||
isLiteralComputedPropertyDeclarationName(node)) {
nameTable[(node).text] = nameTable[(node).text] === undefined ? node.pos : -1;
}
break;
default:
forEachChild(node, walk);
if (node.jsDocComments) {
for (const jsDocComment of node.jsDocComments) {
forEachChild(jsDocComment, walk);
}
}
}
}
}
function isArgumentOfElementAccessExpression(node: Node) {
return node &&
node.parent &&
node.parent.kind === SyntaxKind.ElementAccessExpression &&
(node.parent).argumentExpression === node;
}
/// Classifier
export function createClassifier(): Classifier {
const scanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ false);
/// We do not have a full parser support to know when we should parse a regex or not
/// 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.
const 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;
// Just a stack of TemplateHeads and OpenCurlyBraces, used to perform rudimentary (inexact)
// classification on template strings. Because of the context free nature of templates,
// the only precise way to classify a template portion would be by propagating the stack across
// lines, just as we do with the end-of-line state. However, this is a burden for implementers,
// and the behavior is entirely subsumed by the syntactic classifier anyway, so we instead
// flatten any nesting when the template stack is non-empty and encode it in the end-of-line state.
// Situations in which this fails are
// 1) When template strings are nested across different lines:
// `hello ${ `world
// ` }`
//
// Where on the second line, you will get the closing of a template,
// a closing curly, and a new template.
//
// 2) When substitution expressions have curly braces and the curly brace falls on the next line:
// `hello ${ () => {
// return "world" } } `
//
// Where on the second line, you will get the 'return' keyword,
// a string literal, and a template end consisting of '} } `'.
const templateStack: SyntaxKind[] = [];
/** 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 convertClassifications(classifications: Classifications, text: string): ClassificationResult {
const entries: ClassificationInfo[] = [];
const dense = classifications.spans;
let lastEnd = 0;
for (let i = 0, n = dense.length; i < n; i += 3) {
const start = dense[i];
const length = dense[i + 1];
const type = dense[i + 2];
// Make a whitespace entry between the last item and this one.
if (lastEnd >= 0) {
const whitespaceLength = start - lastEnd;
if (whitespaceLength > 0) {
entries.push({ length: whitespaceLength, classification: TokenClass.Whitespace });
}
}
entries.push({ length, classification: convertClassification(type) });
lastEnd = start + length;
}
const whitespaceLength = text.length - lastEnd;
if (whitespaceLength > 0) {
entries.push({ length: whitespaceLength, classification: TokenClass.Whitespace });
}
return { entries, finalLexState: classifications.endOfLineState };
}
function convertClassification(type: ClassificationType): TokenClass {
switch (type) {
case ClassificationType.comment: return TokenClass.Comment;
case ClassificationType.keyword: return TokenClass.Keyword;
case ClassificationType.numericLiteral: return TokenClass.NumberLiteral;
case ClassificationType.operator: return TokenClass.Operator;
case ClassificationType.stringLiteral: return TokenClass.StringLiteral;
case ClassificationType.whiteSpace: return TokenClass.Whitespace;
case ClassificationType.punctuation: return TokenClass.Punctuation;
case ClassificationType.identifier:
case ClassificationType.className:
case ClassificationType.enumName:
case ClassificationType.interfaceName:
case ClassificationType.moduleName:
case ClassificationType.typeParameterName:
case ClassificationType.typeAliasName:
case ClassificationType.text:
case ClassificationType.parameterName:
default:
return TokenClass.Identifier;
}
}
function getClassificationsForLine(text: string, lexState: EndOfLineState, syntacticClassifierAbsent: boolean): ClassificationResult {
return convertClassifications(getEncodedLexicalClassifications(text, lexState, syntacticClassifierAbsent), text);
}
// If there is a syntactic classifier ('syntacticClassifierAbsent' is false),
// we will be more conservative in order to avoid conflicting with the syntactic classifier.
function getEncodedLexicalClassifications(text: string, lexState: EndOfLineState, syntacticClassifierAbsent: boolean): Classifications {
let offset = 0;
let token = SyntaxKind.Unknown;
let lastNonTriviaToken = SyntaxKind.Unknown;
// Empty out the template stack for reuse.
while (templateStack.length > 0) {
templateStack.pop();
}
// If we're in a string literal, then prepend: "\
// (and a newline). That way when we lex we'll think we're still in a string literal.
//
// If we're in a multiline comment, then prepend: /*
// (and a newline). That way when we lex we'll think we're still in a multiline comment.
switch (lexState) {
case EndOfLineState.InDoubleQuoteStringLiteral:
text = "\"\\\n" + text;
offset = 3;
break;
case EndOfLineState.InSingleQuoteStringLiteral:
text = "'\\\n" + text;
offset = 3;
break;
case EndOfLineState.InMultiLineCommentTrivia:
text = "/*\n" + text;
offset = 3;
break;
case EndOfLineState.InTemplateHeadOrNoSubstitutionTemplate:
text = "`\n" + text;
offset = 2;
break;
case EndOfLineState.InTemplateMiddleOrTail:
text = "}\n" + text;
offset = 2;
// fallthrough
case EndOfLineState.InTemplateSubstitutionPosition:
templateStack.push(SyntaxKind.TemplateHead);
break;
}
scanner.setText(text);
const result: Classifications = {
endOfLineState: EndOfLineState.None,
spans: []
};
// We can run into an unfortunate interaction between the lexical and syntactic classifier
// when the user is typing something generic. Consider the case where the user types:
//
// Foo tokens. It's a weak heuristic, but should
// work well enough in practice.
let angleBracketStack = 0;
do {
token = scanner.scan();
if (!isTrivia(token)) {
if ((token === SyntaxKind.SlashToken || token === SyntaxKind.SlashEqualsToken) && !noRegexTable[lastNonTriviaToken]) {
if (scanner.reScanSlashToken() === SyntaxKind.RegularExpressionLiteral) {
token = SyntaxKind.RegularExpressionLiteral;
}
}
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;
}
else if (lastNonTriviaToken === SyntaxKind.Identifier &&
token === SyntaxKind.LessThanToken) {
// Could be the start of something generic. Keep track of that by bumping
// up the current count of generic contexts we may be in.
angleBracketStack++;
}
else if (token === SyntaxKind.GreaterThanToken && angleBracketStack > 0) {
// If we think we're currently in something generic, then mark that that
// generic entity is complete.
angleBracketStack--;
}
else if (token === SyntaxKind.AnyKeyword ||
token === SyntaxKind.StringKeyword ||
token === SyntaxKind.NumberKeyword ||
token === SyntaxKind.BooleanKeyword ||
token === SyntaxKind.SymbolKeyword) {
if (angleBracketStack > 0 && !syntacticClassifierAbsent) {
// If it looks like we're could be in something generic, don't classify this
// as a keyword. We may just get overwritten by the syntactic classifier,
// causing a noisy experience for the user.
token = SyntaxKind.Identifier;
}
}
else if (token === SyntaxKind.TemplateHead) {
templateStack.push(token);
}
else if (token === SyntaxKind.OpenBraceToken) {
// If we don't have anything on the template stack,
// then we aren't trying to keep track of a previously scanned template head.
if (templateStack.length > 0) {
templateStack.push(token);
}
}
else if (token === SyntaxKind.CloseBraceToken) {
// If we don't have anything on the template stack,
// then we aren't trying to keep track of a previously scanned template head.
if (templateStack.length > 0) {
const lastTemplateStackToken = lastOrUndefined(templateStack);
if (lastTemplateStackToken === SyntaxKind.TemplateHead) {
token = scanner.reScanTemplateToken();
// Only pop on a TemplateTail; a TemplateMiddle indicates there is more for us.
if (token === SyntaxKind.TemplateTail) {
templateStack.pop();
}
else {
Debug.assert(token === SyntaxKind.TemplateMiddle, "Should have been a template middle. Was " + token);
}
}
else {
Debug.assert(lastTemplateStackToken === SyntaxKind.OpenBraceToken, "Should have been an open brace. Was: " + token);
templateStack.pop();
}
}
}
lastNonTriviaToken = token;
}
processToken();
}
while (token !== SyntaxKind.EndOfFileToken);
return result;
function processToken(): void {
const start = scanner.getTokenPos();
const end = scanner.getTextPos();
addResult(start, end, classFromKind(token));
if (end >= text.length) {
if (token === SyntaxKind.StringLiteral || token === SyntaxKind.StringLiteralType) {
// Check to see if we finished up on a multiline string literal.
const tokenText = scanner.getTokenText();
if (scanner.isUnterminated()) {
const lastCharIndex = tokenText.length - 1;
let numBackslashes = 0;
while (tokenText.charCodeAt(lastCharIndex - numBackslashes) === CharacterCodes.backslash) {
numBackslashes++;
}
// If we have an odd number of backslashes, then the multiline string is unclosed
if (numBackslashes & 1) {
const quoteChar = tokenText.charCodeAt(0);
result.endOfLineState = quoteChar === CharacterCodes.doubleQuote
? EndOfLineState.InDoubleQuoteStringLiteral
: EndOfLineState.InSingleQuoteStringLiteral;
}
}
}
else if (token === SyntaxKind.MultiLineCommentTrivia) {
// Check to see if the multiline comment was unclosed.
if (scanner.isUnterminated()) {
result.endOfLineState = EndOfLineState.InMultiLineCommentTrivia;
}
}
else if (isTemplateLiteralKind(token)) {
if (scanner.isUnterminated()) {
if (token === SyntaxKind.TemplateTail) {
result.endOfLineState = EndOfLineState.InTemplateMiddleOrTail;
}
else if (token === SyntaxKind.NoSubstitutionTemplateLiteral) {
result.endOfLineState = EndOfLineState.InTemplateHeadOrNoSubstitutionTemplate;
}
else {
Debug.fail("Only 'NoSubstitutionTemplateLiteral's and 'TemplateTail's can be unterminated; got SyntaxKind #" + token);
}
}
}
else if (templateStack.length > 0 && lastOrUndefined(templateStack) === SyntaxKind.TemplateHead) {
result.endOfLineState = EndOfLineState.InTemplateSubstitutionPosition;
}
}
}
function addResult(start: number, end: number, classification: ClassificationType): void {
if (classification === ClassificationType.whiteSpace) {
// Don't bother with whitespace classifications. They're not needed.
return;
}
if (start === 0 && offset > 0) {
// We're classifying the first token, and this was a case where we prepended
// text. We should consider the start of this token to be at the start of
// the original text.
start += offset;
}
// All our tokens are in relation to the augmented text. Move them back to be
// relative to the original text.
start -= offset;
end -= offset;
const length = end - start;
if (length > 0) {
result.spans.push(start);
result.spans.push(length);
result.spans.push(classification);
}
}
}
function isBinaryExpressionOperatorToken(token: SyntaxKind): boolean {
switch (token) {
case SyntaxKind.AsteriskToken:
case SyntaxKind.SlashToken:
case SyntaxKind.PercentToken:
case SyntaxKind.PlusToken:
case SyntaxKind.MinusToken:
case SyntaxKind.LessThanLessThanToken:
case SyntaxKind.GreaterThanGreaterThanToken:
case SyntaxKind.GreaterThanGreaterThanGreaterThanToken:
case SyntaxKind.LessThanToken:
case SyntaxKind.GreaterThanToken:
case SyntaxKind.LessThanEqualsToken:
case SyntaxKind.GreaterThanEqualsToken:
case SyntaxKind.InstanceOfKeyword:
case SyntaxKind.InKeyword:
case SyntaxKind.AsKeyword:
case SyntaxKind.EqualsEqualsToken:
case SyntaxKind.ExclamationEqualsToken:
case SyntaxKind.EqualsEqualsEqualsToken:
case SyntaxKind.ExclamationEqualsEqualsToken:
case SyntaxKind.AmpersandToken:
case SyntaxKind.CaretToken:
case SyntaxKind.BarToken:
case SyntaxKind.AmpersandAmpersandToken:
case SyntaxKind.BarBarToken:
case SyntaxKind.BarEqualsToken:
case SyntaxKind.AmpersandEqualsToken:
case SyntaxKind.CaretEqualsToken:
case SyntaxKind.LessThanLessThanEqualsToken:
case SyntaxKind.GreaterThanGreaterThanEqualsToken:
case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken:
case SyntaxKind.PlusEqualsToken:
case SyntaxKind.MinusEqualsToken:
case SyntaxKind.AsteriskEqualsToken:
case SyntaxKind.SlashEqualsToken:
case SyntaxKind.PercentEqualsToken:
case SyntaxKind.EqualsToken:
case SyntaxKind.CommaToken:
return true;
default:
return false;
}
}
function isPrefixUnaryExpressionOperatorToken(token: SyntaxKind): boolean {
switch (token) {
case SyntaxKind.PlusToken:
case SyntaxKind.MinusToken:
case SyntaxKind.TildeToken:
case SyntaxKind.ExclamationToken:
case SyntaxKind.PlusPlusToken:
case SyntaxKind.MinusMinusToken:
return true;
default:
return false;
}
}
function isKeyword(token: SyntaxKind): boolean {
return token >= SyntaxKind.FirstKeyword && token <= SyntaxKind.LastKeyword;
}
function classFromKind(token: SyntaxKind): ClassificationType {
if (isKeyword(token)) {
return ClassificationType.keyword;
}
else if (isBinaryExpressionOperatorToken(token) || isPrefixUnaryExpressionOperatorToken(token)) {
return ClassificationType.operator;
}
else if (token >= SyntaxKind.FirstPunctuation && token <= SyntaxKind.LastPunctuation) {
return ClassificationType.punctuation;
}
switch (token) {
case SyntaxKind.NumericLiteral:
return ClassificationType.numericLiteral;
case SyntaxKind.StringLiteral:
case SyntaxKind.StringLiteralType:
return ClassificationType.stringLiteral;
case SyntaxKind.RegularExpressionLiteral:
return ClassificationType.regularExpressionLiteral;
case SyntaxKind.ConflictMarkerTrivia:
case SyntaxKind.MultiLineCommentTrivia:
case SyntaxKind.SingleLineCommentTrivia:
return ClassificationType.comment;
case SyntaxKind.WhitespaceTrivia:
case SyntaxKind.NewLineTrivia:
return ClassificationType.whiteSpace;
case SyntaxKind.Identifier:
default:
if (isTemplateLiteralKind(token)) {
return ClassificationType.stringLiteral;
}
return ClassificationType.identifier;
}
}
return {
getClassificationsForLine,
getEncodedLexicalClassifications
};
}
/// getDefaultLibraryFilePath
declare const __dirname: string;
/**
* Get the path of the default library files (lib.d.ts) as distributed with the typescript
* node package.
* The functionality is not supported if the ts module is consumed outside of a node module.
*/
export function getDefaultLibFilePath(options: CompilerOptions): string {
// Check __dirname is defined and that we are on a node.js system.
if (typeof __dirname !== "undefined") {
return __dirname + directorySeparator + getDefaultLibFileName(options);
}
throw new Error("getDefaultLibFilePath is only supported when consumed as a node module. ");
}
function initializeServices() {
objectAllocator = {
getNodeConstructor: () => NodeObject,
getSourceFileConstructor: () => SourceFileObject,
getSymbolConstructor: () => SymbolObject,
getTypeConstructor: () => TypeObject,
getSignatureConstructor: () => SignatureObject,
};
}
initializeServices();
}