Completion for default export should be '.default' (#16742)

* Completion for default export should be '.default'

* Don't include empty string in name table

* getSymbolsInScope() should return local symbols, not exported symbols

* Fix bug: getSymbolAtLocation should work for local symbol too
This commit is contained in:
Andy
2017-07-11 07:23:32 -07:00
committed by GitHub
parent a94e0c36b0
commit aa2d1008bf
20 changed files with 200 additions and 158 deletions
+3 -6
View File
@@ -11875,6 +11875,8 @@ namespace ts {
}
function getTypeOfSymbolAtLocation(symbol: Symbol, location: Node) {
symbol = symbol.exportSymbol || symbol;
// If we have an identifier or a property access at the given location, if the location is
// an dotted name expression, and if the location is not an assignment target, obtain the type
// of the expression (which will reflect control flow analysis). If the expression indeed
@@ -22281,11 +22283,6 @@ namespace ts {
}
switch (location.kind) {
case SyntaxKind.SourceFile:
if (!isExternalOrCommonJsModule(<SourceFile>location)) {
break;
}
// falls through
case SyntaxKind.ModuleDeclaration:
copySymbols(getSymbolOfNode(location).exports, meaning & SymbolFlags.ModuleMember);
break;
@@ -22337,7 +22334,7 @@ namespace ts {
* @param meaning meaning of symbol to filter by before adding to symbol table
*/
function copySymbol(symbol: Symbol, meaning: SymbolFlags): void {
if (symbol.flags & meaning) {
if (getCombinedLocalAndExportSymbolFlags(symbol) & meaning) {
const id = symbol.name;
// We will copy all symbol regardless of its reserved name because
// symbolsToArray will check whether the key is a reserved name and
+5
View File
@@ -3598,6 +3598,11 @@ namespace ts {
}
return previous[previous.length - 1];
}
/** See comment on `declareModuleMember` in `binder.ts`. */
export function getCombinedLocalAndExportSymbolFlags(symbol: Symbol): SymbolFlags {
return symbol.exportSymbol ? symbol.exportSymbol.flags | symbol.flags : symbol.flags;
}
}
namespace ts {
+24
View File
@@ -949,6 +949,22 @@ namespace FourSlash {
this.verifySymbol(symbol, declarationRanges);
}
public symbolsInScope(range: Range): ts.Symbol[] {
const node = this.goToAndGetNode(range);
return this.getChecker().getSymbolsInScope(node, ts.SymbolFlags.Value | ts.SymbolFlags.Type | ts.SymbolFlags.Namespace);
}
public verifyTypeOfSymbolAtLocation(range: Range, symbol: ts.Symbol, expected: string): void {
const node = this.goToAndGetNode(range);
const checker = this.getChecker();
const type = checker.getTypeOfSymbolAtLocation(symbol, node);
const actual = checker.typeToString(type);
if (actual !== expected) {
this.raiseError(`Expected: '${expected}', actual: '${actual}'`);
}
}
private verifyReferencesAre(expectedReferences: Range[]) {
const actualReferences = this.getReferencesAtCaret() || [];
@@ -3426,6 +3442,10 @@ namespace FourSlashInterface {
public markerByName(s: string): FourSlash.Marker {
return this.state.getMarkerByName(s);
}
public symbolsInScope(range: FourSlash.Range): ts.Symbol[] {
return this.state.symbolsInScope(range);
}
}
export class GoTo {
@@ -3694,6 +3714,10 @@ namespace FourSlashInterface {
this.state.verifySymbolAtLocation(startRange, declarationRanges);
}
public typeOfSymbolAtLocation(range: FourSlash.Range, symbol: ts.Symbol, expected: string) {
this.state.verifyTypeOfSymbolAtLocation(range, symbol, expected);
}
public referencesOf(start: FourSlash.Range, references: FourSlash.Range[]) {
this.state.verifyReferencesOf(start, references);
}
+39 -54
View File
@@ -61,7 +61,7 @@ namespace ts.Completions {
}
else {
if ((!symbols || symbols.length === 0) && keywordFilters === KeywordCompletionFilters.None) {
return undefined;
return undefined;
}
getCompletionEntriesFromSymbols(symbols, entries, location, /*performCharacterChecks*/ true, typeChecker, compilerOptions.target, log);
@@ -112,7 +112,7 @@ namespace ts.Completions {
// 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(typeChecker, symbol, target, performCharacterChecks, location);
const displayName = getCompletionEntryDisplayNameForSymbol(symbol, target, performCharacterChecks);
if (!displayName) {
return undefined;
}
@@ -307,7 +307,7 @@ namespace ts.Completions {
// 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(typeChecker, s, compilerOptions.target, /*performCharacterChecks*/ false, location) === entryName ? s : undefined);
const symbol = forEach(symbols, s => getCompletionEntryDisplayNameForSymbol(s, compilerOptions.target, /*performCharacterChecks*/ false) === entryName ? s : undefined);
if (symbol) {
const { displayParts, documentation, symbolKind, tags } = SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker, symbol, sourceFile, location, location, SemanticMeaning.All);
@@ -341,20 +341,14 @@ namespace ts.Completions {
return undefined;
}
export function getCompletionEntrySymbol(typeChecker: TypeChecker, log: (message: string) => void, compilerOptions: CompilerOptions, sourceFile: SourceFile, position: number, entryName: string): Symbol {
export function getCompletionEntrySymbol(typeChecker: TypeChecker, log: (message: string) => void, compilerOptions: CompilerOptions, sourceFile: SourceFile, position: number, entryName: string): Symbol | undefined {
// Compute all the completion symbols again.
const completionData = getCompletionData(typeChecker, log, sourceFile, position);
if (completionData) {
const { symbols, location } = completionData;
// Find the symbol with the matching entry name.
// 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.
return forEach(symbols, s => getCompletionEntryDisplayNameForSymbol(typeChecker, s, compilerOptions.target, /*performCharacterChecks*/ false, location) === entryName ? s : undefined);
}
return undefined;
// Find the symbol with the matching entry name.
// 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.
return completionData && forEach(completionData.symbols, s => getCompletionEntryDisplayNameForSymbol(s, compilerOptions.target, /*performCharacterChecks*/ false) === entryName ? s : undefined);
}
interface CompletionData {
@@ -369,7 +363,7 @@ namespace ts.Completions {
}
type Request = { kind: "JsDocTagName" } | { kind: "JsDocTag" } | { kind: "JsDocParameterName", tag: JSDocParameterTag };
function getCompletionData(typeChecker: TypeChecker, log: (message: string) => void, sourceFile: SourceFile, position: number): CompletionData {
function getCompletionData(typeChecker: TypeChecker, log: (message: string) => void, sourceFile: SourceFile, position: number): CompletionData | undefined {
const isJavaScriptFile = isSourceFileJavaScript(sourceFile);
let request: Request | undefined;
@@ -615,7 +609,7 @@ namespace ts.Completions {
// Extract module or enum members
const exportedSymbols = typeChecker.getExportsOfModule(symbol);
const isValidValueAccess = (symbol: Symbol) => typeChecker.isValidPropertyAccess(<PropertyAccessExpression>(node.parent), symbol.getUnescapedName());
const isValidTypeAccess = (symbol: Symbol) => symbolCanbeReferencedAtTypeLocation(symbol);
const isValidTypeAccess = (symbol: Symbol) => symbolCanBeReferencedAtTypeLocation(symbol);
const isValidAccess = isRhsOfImportDeclaration ?
// Any kind is allowed when dotting off namespace in internal import equals declaration
(symbol: Symbol) => isValidTypeAccess(symbol) || isValidValueAccess(symbol) :
@@ -630,7 +624,7 @@ namespace ts.Completions {
if (!isTypeLocation) {
const type = typeChecker.getTypeAtLocation(node);
addTypeProperties(type);
if (type) addTypeProperties(type);
}
}
@@ -642,17 +636,17 @@ namespace ts.Completions {
symbols.push(symbol);
}
}
}
if (isJavaScriptFile && type.flags & TypeFlags.Union) {
// In javascript files, for union types, we don't just get the members that
// the individual types have in common, we also include all the members that
// each individual type has. This is because we're going to add all identifiers
// anyways. So we might as well elevate the members that were at least part
// of the individual types to a higher status since we know what they are.
const unionType = <UnionType>type;
for (const elementType of unionType.types) {
addTypeProperties(elementType);
}
if (isJavaScriptFile && type.flags & TypeFlags.Union) {
// In javascript files, for union types, we don't just get the members that
// the individual types have in common, we also include all the members that
// each individual type has. This is because we're going to add all identifiers
// anyways. So we might as well elevate the members that were at least part
// of the individual types to a higher status since we know what they are.
const unionType = <UnionType>type;
for (const elementType of unionType.types) {
addTypeProperties(elementType);
}
}
}
@@ -777,12 +771,12 @@ namespace ts.Completions {
(!isContextTokenValueLocation(contextToken) &&
(isPartOfTypeNode(location) || isContextTokenTypeLocation(contextToken)))) {
// Its a type, but you can reach it by namespace.type as well
return symbolCanbeReferencedAtTypeLocation(symbol);
return symbolCanBeReferencedAtTypeLocation(symbol);
}
}
// expressions are value space (which includes the value namespaces)
return !!(symbol.flags & SymbolFlags.Value);
return !!(getCombinedLocalAndExportSymbolFlags(symbol) & SymbolFlags.Value);
});
}
@@ -812,7 +806,9 @@ namespace ts.Completions {
}
}
function symbolCanbeReferencedAtTypeLocation(symbol: Symbol): boolean {
function symbolCanBeReferencedAtTypeLocation(symbol: Symbol): boolean {
symbol = symbol.exportSymbol || symbol;
// This is an alias, follow what it aliases
if (symbol && symbol.flags & SymbolFlags.Alias) {
symbol = typeChecker.getAliasedSymbol(symbol);
@@ -826,7 +822,7 @@ namespace ts.Completions {
const exportedSymbols = typeChecker.getExportsOfModule(symbol);
// If the exported symbols contains type,
// symbol can be referenced at locations where type is allowed
return forEach(exportedSymbols, symbolCanbeReferencedAtTypeLocation);
return forEach(exportedSymbols, symbolCanBeReferencedAtTypeLocation);
}
}
@@ -1598,22 +1594,23 @@ namespace ts.Completions {
/**
* Get the name to be display in completion from a given symbol.
*
* @return undefined if the name is of external module otherwise a name with striped of any quote
* @return undefined if the name is of external module
*/
function getCompletionEntryDisplayNameForSymbol(typeChecker: TypeChecker, symbol: Symbol, target: ScriptTarget, performCharacterChecks: boolean, location: Node): string {
const displayName: string = getDeclaredName(typeChecker, symbol, location);
function getCompletionEntryDisplayNameForSymbol(symbol: Symbol, target: ScriptTarget, performCharacterChecks: boolean): string | undefined {
const name = symbol.getUnescapedName();
if (!name) return undefined;
if (displayName) {
const firstCharCode = displayName.charCodeAt(0);
// First check of the displayName is not external module; if it is an external module, it is not valid entry
if ((symbol.flags & SymbolFlags.Namespace) && (firstCharCode === CharacterCodes.singleQuote || firstCharCode === CharacterCodes.doubleQuote)) {
// First check of the displayName is not external module; if it is an external module, it is not valid entry
if (symbol.flags & SymbolFlags.Namespace) {
const firstCharCode = name.charCodeAt(0);
if (firstCharCode === CharacterCodes.singleQuote || firstCharCode === CharacterCodes.doubleQuote) {
// If the symbol is external module, don't show it in the completion list
// (i.e declare module "http" { const x; } | // <= request completion here, "http" should not be there)
return undefined;
}
}
return getCompletionEntryDisplayName(displayName, target, performCharacterChecks);
return getCompletionEntryDisplayName(name, target, performCharacterChecks);
}
/**
@@ -1621,24 +1618,12 @@ namespace ts.Completions {
* and checking whether the name is valid identifier name.
*/
function getCompletionEntryDisplayName(name: string, target: ScriptTarget, performCharacterChecks: boolean): string {
if (!name) {
return undefined;
}
name = stripQuotes(name);
if (!name) {
return undefined;
}
// If the user entered name for the symbol was quoted, removing the quotes is not enough, as the name could be an
// invalid identifier name. We need to check if whatever was inside the quotes is actually a valid identifier name.
// e.g "b a" is valid quoted name but when we strip off the quotes, it is invalid.
// We, thus, need to check if whatever was inside the quotes is actually a valid identifier name.
if (performCharacterChecks) {
if (!isIdentifierText(name, target)) {
return undefined;
}
if (performCharacterChecks && !isIdentifierText(name, target)) {
return undefined;
}
return name;
+25 -34
View File
@@ -2063,42 +2063,33 @@ namespace ts {
}
function initializeNameTable(sourceFile: SourceFile): void {
const nameTable = createUnderscoreEscapedMap<number>();
walk(sourceFile);
sourceFile.nameTable = nameTable;
function walk(node: Node) {
switch (node.kind) {
case SyntaxKind.Identifier:
setNameTable((<Identifier>node).text, node);
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)) {
setNameTable(getEscapedTextOfIdentifierOrLiteral((<LiteralExpression>node)), node);
}
break;
default:
forEachChild(node, walk);
if (node.jsDoc) {
for (const jsDoc of node.jsDoc) {
forEachChild(jsDoc, walk);
}
}
const nameTable = sourceFile.nameTable = createUnderscoreEscapedMap<number>();
sourceFile.forEachChild(function walk(node) {
if ((isIdentifier(node) || isStringOrNumericLiteral(node) && literalIsName(node)) && node.text) {
const text = getEscapedTextOfIdentifierOrLiteral(node);
nameTable.set(text, nameTable.get(text) === undefined ? node.pos : -1);
}
}
function setNameTable(text: __String, node: ts.Node): void {
nameTable.set(text, nameTable.get(text) === undefined ? node.pos : -1);
}
forEachChild(node, walk);
if (node.jsDoc) {
for (const jsDoc of node.jsDoc) {
forEachChild(jsDoc, walk);
}
}
});
}
/**
* 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.
*/
function literalIsName(node: ts.StringLiteral | ts.NumericLiteral): boolean {
return isDeclarationName(node) ||
node.parent.kind === SyntaxKind.ExternalModuleReference ||
isArgumentOfElementAccessExpression(node) ||
isLiteralComputedPropertyDeclarationName(node);
}
function isObjectLiteralElement(node: Node): node is ObjectLiteralElement {
+6 -6
View File
@@ -2,7 +2,7 @@
namespace ts.SymbolDisplay {
// TODO(drosen): use contextual SemanticMeaning.
export function getSymbolKind(typeChecker: TypeChecker, symbol: Symbol, location: Node): ScriptElementKind {
const { flags } = symbol;
const flags = getCombinedLocalAndExportSymbolFlags(symbol);
if (flags & SymbolFlags.Class) {
return getDeclarationOfKind(symbol, SyntaxKind.ClassExpression) ?
@@ -34,7 +34,7 @@ namespace ts.SymbolDisplay {
if (location.kind === SyntaxKind.ThisKeyword && isExpression(location)) {
return ScriptElementKind.parameterElement;
}
const { flags } = symbol;
const flags = getCombinedLocalAndExportSymbolFlags(symbol);
if (flags & SymbolFlags.Variable) {
if (isFirstDeclarationOfSymbolParameter(symbol)) {
return ScriptElementKind.parameterElement;
@@ -96,7 +96,7 @@ namespace ts.SymbolDisplay {
const displayParts: SymbolDisplayPart[] = [];
let documentation: SymbolDisplayPart[];
let tags: JSDocTagInfo[];
const symbolFlags = symbol.flags;
const symbolFlags = ts.getCombinedLocalAndExportSymbolFlags(symbol);
let symbolKind = getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker, symbol, location);
let hasAddedSymbolInfo: boolean;
const isThisExpression = location.kind === SyntaxKind.ThisKeyword && isExpression(location);
@@ -110,7 +110,7 @@ namespace ts.SymbolDisplay {
}
let signature: Signature;
type = isThisExpression ? typeChecker.getTypeAtLocation(location) : typeChecker.getTypeOfSymbolAtLocation(symbol, location);
type = isThisExpression ? typeChecker.getTypeAtLocation(location) : typeChecker.getTypeOfSymbolAtLocation(symbol.exportSymbol || symbol, location);
if (type) {
if (location.parent && location.parent.kind === SyntaxKind.PropertyAccessExpression) {
const right = (<PropertyAccessExpression>location.parent).name;
@@ -198,7 +198,7 @@ namespace ts.SymbolDisplay {
hasAddedSymbolInfo = true;
}
}
else if ((isNameOfFunctionDeclaration(location) && !(symbol.flags & SymbolFlags.Accessor)) || // name of function declaration
else if ((isNameOfFunctionDeclaration(location) && !(symbolFlags & 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 = <FunctionLike>location.parent;
@@ -429,7 +429,7 @@ namespace ts.SymbolDisplay {
if (!documentation) {
documentation = symbol.getDocumentationComment();
tags = symbol.getJsDocTags();
if (documentation.length === 0 && symbol.flags & SymbolFlags.Property) {
if (documentation.length === 0 && symbolFlags & SymbolFlags.Property) {
// For some special property access expressions like `exports.foo = foo` or `module.exports.foo = foo`
// there documentation comments might be attached to the right hand side symbol of their declarations.
// The pattern of such special property access is that the parent symbol is the symbol of the file.