mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-06 20:14:01 -06:00
This reverts commit adfdae0dc47db5ad8248d26929a7b31cb895a539, reversing changes made to aad663cebf6a89f7029af57d8cb6a0a011854978.
639 lines
30 KiB
TypeScript
639 lines
30 KiB
TypeScript
/* @internal */
|
|
namespace ts.DocumentHighlights {
|
|
export function getDocumentHighlights(typeChecker: TypeChecker, cancellationToken: CancellationToken, sourceFile: SourceFile, position: number, sourceFilesToSearch: SourceFile[]): DocumentHighlights[] {
|
|
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 = FindAllReferences.getReferencedSymbolsForNode(typeChecker, cancellationToken, node, sourceFilesToSearch, /*findInStrings*/ false, /*findInComments*/ false, /*implementations*/false);
|
|
return convertReferencedSymbols(referencedSymbols);
|
|
|
|
}
|
|
|
|
return undefined;
|
|
|
|
function convertReferencedSymbols(referencedSymbols: ReferencedSymbol[]): DocumentHighlights[] {
|
|
if (!referencedSymbols) {
|
|
return undefined;
|
|
}
|
|
|
|
const fileNameToDocumentHighlights = createMap<DocumentHighlights>();
|
|
const result: DocumentHighlights[] = [];
|
|
for (const referencedSymbol of referencedSymbols) {
|
|
for (const referenceEntry of referencedSymbol.references) {
|
|
const fileName = referenceEntry.fileName;
|
|
let documentHighlights = 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(<IfStatement>node.parent);
|
|
}
|
|
break;
|
|
case SyntaxKind.ReturnKeyword:
|
|
if (hasKind(node.parent, SyntaxKind.ReturnStatement)) {
|
|
return getReturnOccurrences(<ReturnStatement>node.parent);
|
|
}
|
|
break;
|
|
case SyntaxKind.ThrowKeyword:
|
|
if (hasKind(node.parent, SyntaxKind.ThrowStatement)) {
|
|
return getThrowOccurrences(<ThrowStatement>node.parent);
|
|
}
|
|
break;
|
|
case SyntaxKind.CatchKeyword:
|
|
if (hasKind(parent(parent(node)), SyntaxKind.TryStatement)) {
|
|
return getTryCatchFinallyOccurrences(<TryStatement>node.parent.parent);
|
|
}
|
|
break;
|
|
case SyntaxKind.TryKeyword:
|
|
case SyntaxKind.FinallyKeyword:
|
|
if (hasKind(parent(node), SyntaxKind.TryStatement)) {
|
|
return getTryCatchFinallyOccurrences(<TryStatement>node.parent);
|
|
}
|
|
break;
|
|
case SyntaxKind.SwitchKeyword:
|
|
if (hasKind(node.parent, SyntaxKind.SwitchStatement)) {
|
|
return getSwitchCaseDefaultOccurrences(<SwitchStatement>node.parent);
|
|
}
|
|
break;
|
|
case SyntaxKind.CaseKeyword:
|
|
case SyntaxKind.DefaultKeyword:
|
|
if (hasKind(parent(parent(parent(node))), SyntaxKind.SwitchStatement)) {
|
|
return getSwitchCaseDefaultOccurrences(<SwitchStatement>node.parent.parent.parent);
|
|
}
|
|
break;
|
|
case SyntaxKind.BreakKeyword:
|
|
case SyntaxKind.ContinueKeyword:
|
|
if (hasKind(node.parent, SyntaxKind.BreakStatement) || hasKind(node.parent, SyntaxKind.ContinueStatement)) {
|
|
return getBreakOrContinueStatementOccurrences(<BreakOrContinueStatement>node.parent);
|
|
}
|
|
break;
|
|
case SyntaxKind.ForKeyword:
|
|
if (hasKind(node.parent, SyntaxKind.ForStatement) ||
|
|
hasKind(node.parent, SyntaxKind.ForInStatement) ||
|
|
hasKind(node.parent, SyntaxKind.ForOfStatement)) {
|
|
return getLoopBreakContinueOccurrences(<IterationStatement>node.parent);
|
|
}
|
|
break;
|
|
case SyntaxKind.WhileKeyword:
|
|
case SyntaxKind.DoKeyword:
|
|
if (hasKind(node.parent, SyntaxKind.WhileStatement) || hasKind(node.parent, SyntaxKind.DoStatement)) {
|
|
return getLoopBreakContinueOccurrences(<IterationStatement>node.parent);
|
|
}
|
|
break;
|
|
case SyntaxKind.ConstructorKeyword:
|
|
if (hasKind(node.parent, SyntaxKind.Constructor)) {
|
|
return getConstructorOccurrences(<ConstructorDeclaration>node.parent);
|
|
}
|
|
break;
|
|
case SyntaxKind.GetKeyword:
|
|
case SyntaxKind.SetKeyword:
|
|
if (hasKind(node.parent, SyntaxKind.GetAccessor) || hasKind(node.parent, SyntaxKind.SetAccessor)) {
|
|
return getGetAndSetOccurrences(<AccessorDeclaration>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(<ThrowStatement>node);
|
|
}
|
|
else if (node.kind === SyntaxKind.TryStatement) {
|
|
const tryStatement = <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 = <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(<BreakOrContinueStatement>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: ModifierFlags = 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 & ModifierFlags.Abstract) {
|
|
nodes = (<Node[]>(<ClassDeclaration>declaration).members).concat(declaration);
|
|
}
|
|
else {
|
|
nodes = (<Block>container).statements;
|
|
}
|
|
break;
|
|
case SyntaxKind.Constructor:
|
|
nodes = (<Node[]>(<ConstructorDeclaration>container).parameters).concat(
|
|
(<ClassDeclaration>container.parent).members);
|
|
break;
|
|
case SyntaxKind.ClassDeclaration:
|
|
case SyntaxKind.ClassExpression:
|
|
nodes = (<ClassLikeDeclaration>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 & ModifierFlags.AccessibilityModifier) {
|
|
const constructor = forEach((<ClassLikeDeclaration>container).members, member => {
|
|
return member.kind === SyntaxKind.Constructor && <ConstructorDeclaration>member;
|
|
});
|
|
|
|
if (constructor) {
|
|
nodes = nodes.concat(constructor.parameters);
|
|
}
|
|
}
|
|
else if (modifierFlag & ModifierFlags.Abstract) {
|
|
nodes = nodes.concat(container);
|
|
}
|
|
break;
|
|
default:
|
|
Debug.fail("Invalid container kind.");
|
|
}
|
|
|
|
forEach(nodes, node => {
|
|
if (getModifierFlags(node) & modifierFlag) {
|
|
forEach(node.modifiers, child => pushKeywordIf(keywords, child, modifier));
|
|
}
|
|
});
|
|
|
|
return map(keywords, getHighlightSpanForNode);
|
|
|
|
function getFlagFromModifier(modifier: SyntaxKind) {
|
|
switch (modifier) {
|
|
case SyntaxKind.PublicKeyword:
|
|
return ModifierFlags.Public;
|
|
case SyntaxKind.PrivateKeyword:
|
|
return ModifierFlags.Private;
|
|
case SyntaxKind.ProtectedKeyword:
|
|
return ModifierFlags.Protected;
|
|
case SyntaxKind.StaticKeyword:
|
|
return ModifierFlags.Static;
|
|
case SyntaxKind.ExportKeyword:
|
|
return ModifierFlags.Export;
|
|
case SyntaxKind.DeclareKeyword:
|
|
return ModifierFlags.Ambient;
|
|
case SyntaxKind.AbstractKeyword:
|
|
return ModifierFlags.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(<IterationStatement>owner);
|
|
case SyntaxKind.SwitchStatement:
|
|
return getSwitchCaseDefaultOccurrences(<SwitchStatement>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(<Block>owner, returnStatement => {
|
|
pushKeywordIf(keywords, returnStatement.getFirstToken(), SyntaxKind.ReturnKeyword);
|
|
});
|
|
}
|
|
|
|
return map(keywords, getHighlightSpanForNode);
|
|
}
|
|
|
|
function getReturnOccurrences(returnStatement: ReturnStatement): HighlightSpan[] {
|
|
const func = <FunctionLikeDeclaration>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(<Block>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>ifStatement.parent).elseStatement === ifStatement) {
|
|
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>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 (!isWhiteSpaceSingleLine(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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Whether or not a 'node' is preceded by a label of the given string.
|
|
* Note: 'node' cannot be a SourceFile.
|
|
*/
|
|
function isLabeledBy(node: Node, labelName: string) {
|
|
for (let owner = node.parent; owner.kind === SyntaxKind.LabeledStatement; owner = owner.parent) {
|
|
if ((<LabeledStatement>owner).label.text === labelName) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|