-
Notifications
You must be signed in to change notification settings - Fork 12.9k
Adds quick info on modifiers and declaration keywords #3189
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1148,11 +1148,13 @@ module ts { | |
|
||
getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[]; | ||
getSymbolAtLocation(node: Node): Symbol; | ||
getSymbolFromDeclarationKeyword(node: Node): Symbol; | ||
getShorthandAssignmentValueSymbol(location: Node): Symbol; | ||
getTypeAtLocation(node: Node): Type; | ||
typeToString(type: Type, enclosingDeclaration?: Node, flags?: TypeFormatFlags): string; | ||
symbolToString(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags): string; | ||
getSymbolDisplayBuilder(): SymbolDisplayBuilder; | ||
getContextualSignature(node: FunctionExpression | MethodDeclaration): Signature; | ||
getFullyQualifiedName(symbol: Symbol): string; | ||
getAugmentedPropertiesOfType(type: Type): Symbol[]; | ||
getRootSymbols(symbol: Symbol): Symbol[]; | ||
|
@@ -1377,6 +1379,7 @@ module ts { | |
/* @internal */ exportSymbol?: Symbol; // Exported symbol associated with this symbol | ||
valueDeclaration?: Declaration; // First value declaration of the symbol | ||
/* @internal */ constEnumOnlyModule?: boolean; // True if module contains only const enums or other modules with only const enums | ||
isAnonymous?: boolean; // True if declaration is anonymous | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why do you need this? this information can be easily inferred from other sources, like the name, and the node flags. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mhegazy I was trying a lot of different ways to pinpoint anonymous declarations. I just thought this was easier. But you're right, we shouldn't add something just to serve one very specific use case. It would be good if you could help me out on the below section. Where I use this property to add a |
||
} | ||
|
||
/* @internal */ | ||
|
@@ -1455,7 +1458,7 @@ module ts { | |
/* @internal */ | ||
ContainsUndefinedOrNull = 0x00040000, // Type is or contains Undefined or Null type | ||
/* @internal */ | ||
ContainsObjectLiteral = 0x00080000, // Type is or contains object literal type | ||
ContainsObjectLiteral = 0x00080000, // Type is or contains object literal type | ||
ESSymbol = 0x00100000, // Type of symbol primitive introduced in ES6 | ||
|
||
/* @internal */ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -234,7 +234,7 @@ module ts.formatting { | |
// then kind needs to be fixed. This might happen in cases | ||
// when parser interprets token differently, i.e keyword treated as identifier | ||
function fixTokenKind(tokenInfo: TokenInfo, container: Node): TokenInfo { | ||
if (isToken(container) && tokenInfo.token.kind !== container.kind) { | ||
if (isReservedWord(container) && tokenInfo.token.kind !== container.kind) { | ||
tokenInfo.token.kind = container.kind; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we want Token here.. @vladima? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I just renamed isToken to isReservedWord. I thought |
||
} | ||
return tokenInfo; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -197,8 +197,6 @@ module ts { | |
let list = createNode(SyntaxKind.SyntaxList, nodes.pos, nodes.end, NodeFlags.Synthetic, this); | ||
list._children = []; | ||
let pos = nodes.pos; | ||
|
||
|
||
|
||
for (let node of nodes) { | ||
if (pos < node.pos) { | ||
|
@@ -3672,7 +3670,7 @@ module ts { | |
addFullSymbolName(symbol); | ||
writeTypeParametersOfSymbol(symbol, sourceFile); | ||
} | ||
if ((symbolFlags & SymbolFlags.Interface) && (semanticMeaning & SemanticMeaning.Type)) { | ||
if (symbolFlags & SymbolFlags.Interface) { | ||
addNewLineIfDisplayPartsExist(); | ||
displayParts.push(keywordPart(SyntaxKind.InterfaceKeyword)); | ||
displayParts.push(spacePart()); | ||
|
@@ -3839,7 +3837,9 @@ module ts { | |
if (symbolKind) { | ||
pushTypePart(symbolKind); | ||
displayParts.push(spacePart()); | ||
addFullSymbolName(symbol); | ||
if (!symbol.isAnonymous) { | ||
addFullSymbolName(symbol); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mhegazy here is where I build the display part and I use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Something like: node.name ? node.name.text : node.flags & NodeFlags.Default ? "default" : "" There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You only got the And the Also as I recall it the symbol name for a default function is just default. I can't just check for That's why I added the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. first sorry for the delay, just recovering from a long vacation :D. i am not sure i understand what isAnonymous gets you that a check for name does not. so something like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and if understand correctly, what we are trying to handle here is export default function and class declarations with no name, and this change is looking much bigger than the scope of the original problem. in my mind these two cases can be easily special cased in getQuickInfo. |
||
} | ||
} | ||
|
||
|
@@ -3852,6 +3852,9 @@ module ts { | |
case ScriptElementKind.constructorImplementationElement: | ||
displayParts.push(textOrKeywordPart(symbolKind)); | ||
return; | ||
case ScriptElementKind.localFunctionElement: | ||
displayParts.push(textOrKeywordPart(ScriptElementKind.functionElement)); | ||
return; | ||
default: | ||
displayParts.push(punctuationPart(SyntaxKind.OpenParenToken)); | ||
displayParts.push(textOrKeywordPart(symbolKind)); | ||
|
@@ -3882,11 +3885,22 @@ module ts { | |
} | ||
} | ||
|
||
function getQuickInfoNodeBySkippingModifiersAndHeritageKeywords(sourceFile: SourceFile, position: number, beignWithLastTokenPath = false): Node { | ||
let node = getTouchingPropertyName(sourceFile, position, beignWithLastTokenPath); | ||
if (!node) { | ||
return undefined; | ||
} | ||
else if (isModifier(node.kind) || isHeritageKeyword(node)) { | ||
node = getQuickInfoNodeBySkippingModifiersAndHeritageKeywords(sourceFile, node.end + 1, /*beignWithLastTokenPath*/ true); | ||
} | ||
return node; | ||
} | ||
|
||
function getQuickInfoAtPosition(fileName: string, position: number): QuickInfo { | ||
synchronizeHostData(); | ||
|
||
let sourceFile = getValidSourceFile(fileName); | ||
let node = getTouchingPropertyName(sourceFile, position); | ||
let node = getQuickInfoNodeBySkippingModifiersAndHeritageKeywords(sourceFile, position); | ||
if (!node) { | ||
return undefined; | ||
} | ||
|
@@ -3897,6 +3911,9 @@ module ts { | |
|
||
let typeChecker = program.getTypeChecker(); | ||
let symbol = typeChecker.getSymbolAtLocation(node); | ||
if(!symbol && isDeclarationKeyword(node)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. as i mentioned earlier, i would check for function keyword here and special case it. |
||
symbol = typeChecker.getSymbolFromDeclarationKeyword(node); | ||
} | ||
|
||
if (!symbol) { | ||
// Try getting just type at this position and show | ||
|
@@ -6228,7 +6245,7 @@ module ts { | |
if (textSpanIntersectsWith(span, element.getFullStart(), element.getFullWidth())) { | ||
let children = element.getChildren(); | ||
for (let child of children) { | ||
if (isToken(child)) { | ||
if (isReservedWord(child)) { | ||
classifyToken(child); | ||
} | ||
else { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -266,37 +266,45 @@ module ts { | |
/* Gets the token whose text has range [start, end) and position >= start | ||
* and (position < end or (position === end && token is keyword or identifier or numeric\string litera)) | ||
*/ | ||
export function getTouchingPropertyName(sourceFile: SourceFile, position: number): Node { | ||
return getTouchingToken(sourceFile, position, n => isPropertyName(n.kind)); | ||
export function getTouchingPropertyName(sourceFile: SourceFile, position: number, beignWithLastTokenPath = false): Node { | ||
return getTouchingToken(sourceFile, position, n => isPropertyName(n.kind), /*beignWithLastTokenPath*/ beignWithLastTokenPath); | ||
} | ||
|
||
/** Returns the token if position is in [start, end) or if position === end and includeItemAtEndPosition(token) === true */ | ||
export function getTouchingToken(sourceFile: SourceFile, position: number, includeItemAtEndPosition?: (n: Node) => boolean): Node { | ||
return getTokenAtPositionWorker(sourceFile, position, /*allowPositionInLeadingTrivia*/ false, includeItemAtEndPosition); | ||
export function getTouchingToken(sourceFile: SourceFile, position: number, includeItemAtEndPosition?: (n: Node) => boolean, beignWithLastTokenPath = false): Node { | ||
return getTokenAtPositionWorker(sourceFile, position, /*allowPositionInLeadingTrivia*/ false, includeItemAtEndPosition, /*beignWithLastTokenPath*/ beignWithLastTokenPath); | ||
} | ||
|
||
/** Returns a token if position is in [start-of-leading-trivia, end) */ | ||
export function getTokenAtPosition(sourceFile: SourceFile, position: number): Node { | ||
return getTokenAtPositionWorker(sourceFile, position, /*allowPositionInLeadingTrivia*/ true, /*includeItemAtEndPosition*/ undefined); | ||
} | ||
|
||
export let lastTokenPath: { [depth: number]: number } = {}; | ||
|
||
/** Get the token whose text contains the position */ | ||
function getTokenAtPositionWorker(sourceFile: SourceFile, position: number, allowPositionInLeadingTrivia: boolean, includeItemAtEndPosition: (n: Node) => boolean): Node { | ||
function getTokenAtPositionWorker(sourceFile: SourceFile, position: number, allowPositionInLeadingTrivia: boolean, includeItemAtEndPosition: (n: Node) => boolean, beignWithLastTokenPath = false): Node { | ||
let current: Node = sourceFile; | ||
let depthLevel = 0; | ||
if(!beignWithLastTokenPath) { | ||
lastTokenPath = {}; | ||
} | ||
outer: while (true) { | ||
if (isToken(current)) { | ||
// exit early | ||
if (isReservedWord(current)) { | ||
return current; | ||
} | ||
|
||
// find the child that contains 'position' | ||
|
||
// Find the child that contains 'position' and it will begin with the old token path if specified. | ||
let start = beignWithLastTokenPath && lastTokenPath[depthLevel] ? lastTokenPath[depthLevel] : 0; | ||
for (let i = 0, n = current.getChildCount(sourceFile); i < n; i++) { | ||
let child = current.getChildAt(i); | ||
let start = allowPositionInLeadingTrivia ? child.getFullStart() : child.getStart(sourceFile); | ||
if (start <= position) { | ||
let end = child.getEnd(); | ||
if (position < end || (position === end && child.kind === SyntaxKind.EndOfFileToken)) { | ||
current = child; | ||
lastTokenPath[depthLevel] = i; | ||
depthLevel++; | ||
continue outer; | ||
} | ||
else if (includeItemAtEndPosition && end === position) { | ||
|
@@ -323,7 +331,7 @@ module ts { | |
// Ideally, getTokenAtPosition should return a token. However, it is currently | ||
// broken, so we do a check to make sure the result was indeed a token. | ||
let tokenAtPosition = getTokenAtPosition(file, position); | ||
if (isToken(tokenAtPosition) && position > tokenAtPosition.getStart(file) && position < tokenAtPosition.getEnd()) { | ||
if (isReservedWord(tokenAtPosition) && position > tokenAtPosition.getStart(file) && position < tokenAtPosition.getEnd()) { | ||
return tokenAtPosition; | ||
} | ||
|
||
|
@@ -334,7 +342,7 @@ module ts { | |
return find(parent); | ||
|
||
function find(n: Node): Node { | ||
if (isToken(n) && n.pos === previousToken.end) { | ||
if (isReservedWord(n) && n.pos === previousToken.end) { | ||
// this is token that starts at the end of previous token - return it | ||
return n; | ||
} | ||
|
@@ -360,7 +368,7 @@ module ts { | |
return find(startNode || sourceFile); | ||
|
||
function findRightmostToken(n: Node): Node { | ||
if (isToken(n)) { | ||
if (isReservedWord(n)) { | ||
return n; | ||
} | ||
|
||
|
@@ -371,7 +379,7 @@ module ts { | |
} | ||
|
||
function find(n: Node): Node { | ||
if (isToken(n)) { | ||
if (isReservedWord(n)) { | ||
return n; | ||
} | ||
|
||
|
@@ -447,7 +455,7 @@ module ts { | |
return undefined; | ||
} | ||
|
||
export function isToken(n: Node): boolean { | ||
export function isReservedWord(n: Node): boolean { | ||
return n.kind >= SyntaxKind.FirstToken && n.kind <= SyntaxKind.LastToken; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sorry just saw that. that is fine then. |
||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why enable all these keywords? We used to that and found it to be noisy, specially that QuickInfo is triggered whenever the mouse moves. function expressions is a different story, they do not have a name, so there is no target to land on any ways. that is not the case for all other declarations, they all have names.
i would not enable all these, and only special case
function
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, shall I only special case it on anonymous functions too? Because if we enable it on
function name() {}
then to be consistent — all declaration keywords should have quick info.