Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit aa2d100

Browse filesBrowse files
author
Andy
authored
Completion for default export should be '.default' (microsoft#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
1 parent a94e0c3 commit aa2d100
Copy full SHA for aa2d100

20 files changed

+200-158Lines changed: 200 additions & 158 deletions
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎src/compiler/checker.ts‎

Copy file name to clipboardExpand all lines: src/compiler/checker.ts
+3-6Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11875,6 +11875,8 @@ namespace ts {
1187511875
}
1187611876

1187711877
function getTypeOfSymbolAtLocation(symbol: Symbol, location: Node) {
11878+
symbol = symbol.exportSymbol || symbol;
11879+
1187811880
// If we have an identifier or a property access at the given location, if the location is
1187911881
// an dotted name expression, and if the location is not an assignment target, obtain the type
1188011882
// of the expression (which will reflect control flow analysis). If the expression indeed
@@ -22281,11 +22283,6 @@ namespace ts {
2228122283
}
2228222284

2228322285
switch (location.kind) {
22284-
case SyntaxKind.SourceFile:
22285-
if (!isExternalOrCommonJsModule(<SourceFile>location)) {
22286-
break;
22287-
}
22288-
// falls through
2228922286
case SyntaxKind.ModuleDeclaration:
2229022287
copySymbols(getSymbolOfNode(location).exports, meaning & SymbolFlags.ModuleMember);
2229122288
break;
@@ -22337,7 +22334,7 @@ namespace ts {
2233722334
* @param meaning meaning of symbol to filter by before adding to symbol table
2233822335
*/
2233922336
function copySymbol(symbol: Symbol, meaning: SymbolFlags): void {
22340-
if (symbol.flags & meaning) {
22337+
if (getCombinedLocalAndExportSymbolFlags(symbol) & meaning) {
2234122338
const id = symbol.name;
2234222339
// We will copy all symbol regardless of its reserved name because
2234322340
// symbolsToArray will check whether the key is a reserved name and
Collapse file

‎src/compiler/utilities.ts‎

Copy file name to clipboardExpand all lines: src/compiler/utilities.ts
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3598,6 +3598,11 @@ namespace ts {
35983598
}
35993599
return previous[previous.length - 1];
36003600
}
3601+
3602+
/** See comment on `declareModuleMember` in `binder.ts`. */
3603+
export function getCombinedLocalAndExportSymbolFlags(symbol: Symbol): SymbolFlags {
3604+
return symbol.exportSymbol ? symbol.exportSymbol.flags | symbol.flags : symbol.flags;
3605+
}
36013606
}
36023607

36033608
namespace ts {
Collapse file

‎src/harness/fourslash.ts‎

Copy file name to clipboardExpand all lines: src/harness/fourslash.ts
+24Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -949,6 +949,22 @@ namespace FourSlash {
949949
this.verifySymbol(symbol, declarationRanges);
950950
}
951951

952+
public symbolsInScope(range: Range): ts.Symbol[] {
953+
const node = this.goToAndGetNode(range);
954+
return this.getChecker().getSymbolsInScope(node, ts.SymbolFlags.Value | ts.SymbolFlags.Type | ts.SymbolFlags.Namespace);
955+
}
956+
957+
public verifyTypeOfSymbolAtLocation(range: Range, symbol: ts.Symbol, expected: string): void {
958+
const node = this.goToAndGetNode(range);
959+
const checker = this.getChecker();
960+
const type = checker.getTypeOfSymbolAtLocation(symbol, node);
961+
962+
const actual = checker.typeToString(type);
963+
if (actual !== expected) {
964+
this.raiseError(`Expected: '${expected}', actual: '${actual}'`);
965+
}
966+
}
967+
952968
private verifyReferencesAre(expectedReferences: Range[]) {
953969
const actualReferences = this.getReferencesAtCaret() || [];
954970

@@ -3426,6 +3442,10 @@ namespace FourSlashInterface {
34263442
public markerByName(s: string): FourSlash.Marker {
34273443
return this.state.getMarkerByName(s);
34283444
}
3445+
3446+
public symbolsInScope(range: FourSlash.Range): ts.Symbol[] {
3447+
return this.state.symbolsInScope(range);
3448+
}
34293449
}
34303450

34313451
export class GoTo {
@@ -3694,6 +3714,10 @@ namespace FourSlashInterface {
36943714
this.state.verifySymbolAtLocation(startRange, declarationRanges);
36953715
}
36963716

3717+
public typeOfSymbolAtLocation(range: FourSlash.Range, symbol: ts.Symbol, expected: string) {
3718+
this.state.verifyTypeOfSymbolAtLocation(range, symbol, expected);
3719+
}
3720+
36973721
public referencesOf(start: FourSlash.Range, references: FourSlash.Range[]) {
36983722
this.state.verifyReferencesOf(start, references);
36993723
}
Collapse file

‎src/services/completions.ts‎

Copy file name to clipboardExpand all lines: src/services/completions.ts
+40-55Lines changed: 40 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ namespace ts.Completions {
6161
}
6262
else {
6363
if ((!symbols || symbols.length === 0) && keywordFilters === KeywordCompletionFilters.None) {
64-
return undefined;
64+
return undefined;
6565
}
6666

6767
getCompletionEntriesFromSymbols(symbols, entries, location, /*performCharacterChecks*/ true, typeChecker, compilerOptions.target, log);
@@ -112,7 +112,7 @@ namespace ts.Completions {
112112
// Try to get a valid display name for this symbol, if we could not find one, then ignore it.
113113
// We would like to only show things that can be added after a dot, so for instance numeric properties can
114114
// not be accessed with a dot (a.1 <- invalid)
115-
const displayName = getCompletionEntryDisplayNameForSymbol(typeChecker, symbol, target, performCharacterChecks, location);
115+
const displayName = getCompletionEntryDisplayNameForSymbol(symbol, target, performCharacterChecks);
116116
if (!displayName) {
117117
return undefined;
118118
}
@@ -307,7 +307,7 @@ namespace ts.Completions {
307307
// We don't need to perform character checks here because we're only comparing the
308308
// name against 'entryName' (which is known to be good), not building a new
309309
// completion entry.
310-
const symbol = forEach(symbols, s => getCompletionEntryDisplayNameForSymbol(typeChecker, s, compilerOptions.target, /*performCharacterChecks*/ false, location) === entryName ? s : undefined);
310+
const symbol = forEach(symbols, s => getCompletionEntryDisplayNameForSymbol(s, compilerOptions.target, /*performCharacterChecks*/ false) === entryName ? s : undefined);
311311

312312
if (symbol) {
313313
const { displayParts, documentation, symbolKind, tags } = SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker, symbol, sourceFile, location, location, SemanticMeaning.All);
@@ -341,20 +341,14 @@ namespace ts.Completions {
341341
return undefined;
342342
}
343343

344-
export function getCompletionEntrySymbol(typeChecker: TypeChecker, log: (message: string) => void, compilerOptions: CompilerOptions, sourceFile: SourceFile, position: number, entryName: string): Symbol {
344+
export function getCompletionEntrySymbol(typeChecker: TypeChecker, log: (message: string) => void, compilerOptions: CompilerOptions, sourceFile: SourceFile, position: number, entryName: string): Symbol | undefined {
345345
// Compute all the completion symbols again.
346346
const completionData = getCompletionData(typeChecker, log, sourceFile, position);
347-
if (completionData) {
348-
const { symbols, location } = completionData;
349-
350-
// Find the symbol with the matching entry name.
351-
// We don't need to perform character checks here because we're only comparing the
352-
// name against 'entryName' (which is known to be good), not building a new
353-
// completion entry.
354-
return forEach(symbols, s => getCompletionEntryDisplayNameForSymbol(typeChecker, s, compilerOptions.target, /*performCharacterChecks*/ false, location) === entryName ? s : undefined);
355-
}
356-
357-
return undefined;
347+
// Find the symbol with the matching entry name.
348+
// We don't need to perform character checks here because we're only comparing the
349+
// name against 'entryName' (which is known to be good), not building a new
350+
// completion entry.
351+
return completionData && forEach(completionData.symbols, s => getCompletionEntryDisplayNameForSymbol(s, compilerOptions.target, /*performCharacterChecks*/ false) === entryName ? s : undefined);
358352
}
359353

360354
interface CompletionData {
@@ -369,7 +363,7 @@ namespace ts.Completions {
369363
}
370364
type Request = { kind: "JsDocTagName" } | { kind: "JsDocTag" } | { kind: "JsDocParameterName", tag: JSDocParameterTag };
371365

372-
function getCompletionData(typeChecker: TypeChecker, log: (message: string) => void, sourceFile: SourceFile, position: number): CompletionData {
366+
function getCompletionData(typeChecker: TypeChecker, log: (message: string) => void, sourceFile: SourceFile, position: number): CompletionData | undefined {
373367
const isJavaScriptFile = isSourceFileJavaScript(sourceFile);
374368

375369
let request: Request | undefined;
@@ -615,7 +609,7 @@ namespace ts.Completions {
615609
// Extract module or enum members
616610
const exportedSymbols = typeChecker.getExportsOfModule(symbol);
617611
const isValidValueAccess = (symbol: Symbol) => typeChecker.isValidPropertyAccess(<PropertyAccessExpression>(node.parent), symbol.getUnescapedName());
618-
const isValidTypeAccess = (symbol: Symbol) => symbolCanbeReferencedAtTypeLocation(symbol);
612+
const isValidTypeAccess = (symbol: Symbol) => symbolCanBeReferencedAtTypeLocation(symbol);
619613
const isValidAccess = isRhsOfImportDeclaration ?
620614
// Any kind is allowed when dotting off namespace in internal import equals declaration
621615
(symbol: Symbol) => isValidTypeAccess(symbol) || isValidValueAccess(symbol) :
@@ -630,7 +624,7 @@ namespace ts.Completions {
630624

631625
if (!isTypeLocation) {
632626
const type = typeChecker.getTypeAtLocation(node);
633-
addTypeProperties(type);
627+
if (type) addTypeProperties(type);
634628
}
635629
}
636630

@@ -642,17 +636,17 @@ namespace ts.Completions {
642636
symbols.push(symbol);
643637
}
644638
}
639+
}
645640

646-
if (isJavaScriptFile && type.flags & TypeFlags.Union) {
647-
// In javascript files, for union types, we don't just get the members that
648-
// the individual types have in common, we also include all the members that
649-
// each individual type has. This is because we're going to add all identifiers
650-
// anyways. So we might as well elevate the members that were at least part
651-
// of the individual types to a higher status since we know what they are.
652-
const unionType = <UnionType>type;
653-
for (const elementType of unionType.types) {
654-
addTypeProperties(elementType);
655-
}
641+
if (isJavaScriptFile && type.flags & TypeFlags.Union) {
642+
// In javascript files, for union types, we don't just get the members that
643+
// the individual types have in common, we also include all the members that
644+
// each individual type has. This is because we're going to add all identifiers
645+
// anyways. So we might as well elevate the members that were at least part
646+
// of the individual types to a higher status since we know what they are.
647+
const unionType = <UnionType>type;
648+
for (const elementType of unionType.types) {
649+
addTypeProperties(elementType);
656650
}
657651
}
658652
}
@@ -777,12 +771,12 @@ namespace ts.Completions {
777771
(!isContextTokenValueLocation(contextToken) &&
778772
(isPartOfTypeNode(location) || isContextTokenTypeLocation(contextToken)))) {
779773
// Its a type, but you can reach it by namespace.type as well
780-
return symbolCanbeReferencedAtTypeLocation(symbol);
774+
return symbolCanBeReferencedAtTypeLocation(symbol);
781775
}
782776
}
783777

784778
// expressions are value space (which includes the value namespaces)
785-
return !!(symbol.flags & SymbolFlags.Value);
779+
return !!(getCombinedLocalAndExportSymbolFlags(symbol) & SymbolFlags.Value);
786780
});
787781
}
788782

@@ -812,7 +806,9 @@ namespace ts.Completions {
812806
}
813807
}
814808

815-
function symbolCanbeReferencedAtTypeLocation(symbol: Symbol): boolean {
809+
function symbolCanBeReferencedAtTypeLocation(symbol: Symbol): boolean {
810+
symbol = symbol.exportSymbol || symbol;
811+
816812
// This is an alias, follow what it aliases
817813
if (symbol && symbol.flags & SymbolFlags.Alias) {
818814
symbol = typeChecker.getAliasedSymbol(symbol);
@@ -826,7 +822,7 @@ namespace ts.Completions {
826822
const exportedSymbols = typeChecker.getExportsOfModule(symbol);
827823
// If the exported symbols contains type,
828824
// symbol can be referenced at locations where type is allowed
829-
return forEach(exportedSymbols, symbolCanbeReferencedAtTypeLocation);
825+
return forEach(exportedSymbols, symbolCanBeReferencedAtTypeLocation);
830826
}
831827
}
832828

@@ -1598,47 +1594,36 @@ namespace ts.Completions {
15981594
/**
15991595
* Get the name to be display in completion from a given symbol.
16001596
*
1601-
* @return undefined if the name is of external module otherwise a name with striped of any quote
1597+
* @return undefined if the name is of external module
16021598
*/
1603-
function getCompletionEntryDisplayNameForSymbol(typeChecker: TypeChecker, symbol: Symbol, target: ScriptTarget, performCharacterChecks: boolean, location: Node): string {
1604-
const displayName: string = getDeclaredName(typeChecker, symbol, location);
1605-
1606-
if (displayName) {
1607-
const firstCharCode = displayName.charCodeAt(0);
1608-
// First check of the displayName is not external module; if it is an external module, it is not valid entry
1609-
if ((symbol.flags & SymbolFlags.Namespace) && (firstCharCode === CharacterCodes.singleQuote || firstCharCode === CharacterCodes.doubleQuote)) {
1599+
function getCompletionEntryDisplayNameForSymbol(symbol: Symbol, target: ScriptTarget, performCharacterChecks: boolean): string | undefined {
1600+
const name = symbol.getUnescapedName();
1601+
if (!name) return undefined;
1602+
1603+
// First check of the displayName is not external module; if it is an external module, it is not valid entry
1604+
if (symbol.flags & SymbolFlags.Namespace) {
1605+
const firstCharCode = name.charCodeAt(0);
1606+
if (firstCharCode === CharacterCodes.singleQuote || firstCharCode === CharacterCodes.doubleQuote) {
16101607
// If the symbol is external module, don't show it in the completion list
16111608
// (i.e declare module "http" { const x; } | // <= request completion here, "http" should not be there)
16121609
return undefined;
16131610
}
16141611
}
16151612

1616-
return getCompletionEntryDisplayName(displayName, target, performCharacterChecks);
1613+
return getCompletionEntryDisplayName(name, target, performCharacterChecks);
16171614
}
16181615

16191616
/**
16201617
* Get a displayName from a given for completion list, performing any necessary quotes stripping
16211618
* and checking whether the name is valid identifier name.
16221619
*/
16231620
function getCompletionEntryDisplayName(name: string, target: ScriptTarget, performCharacterChecks: boolean): string {
1624-
if (!name) {
1625-
return undefined;
1626-
}
1627-
1628-
name = stripQuotes(name);
1629-
1630-
if (!name) {
1631-
return undefined;
1632-
}
1633-
16341621
// If the user entered name for the symbol was quoted, removing the quotes is not enough, as the name could be an
16351622
// invalid identifier name. We need to check if whatever was inside the quotes is actually a valid identifier name.
16361623
// e.g "b a" is valid quoted name but when we strip off the quotes, it is invalid.
16371624
// We, thus, need to check if whatever was inside the quotes is actually a valid identifier name.
1638-
if (performCharacterChecks) {
1639-
if (!isIdentifierText(name, target)) {
1640-
return undefined;
1641-
}
1625+
if (performCharacterChecks && !isIdentifierText(name, target)) {
1626+
return undefined;
16421627
}
16431628

16441629
return name;
Collapse file

‎src/services/services.ts‎

Copy file name to clipboardExpand all lines: src/services/services.ts
+24-33Lines changed: 24 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2063,42 +2063,33 @@ namespace ts {
20632063
}
20642064

20652065
function initializeNameTable(sourceFile: SourceFile): void {
2066-
const nameTable = createUnderscoreEscapedMap<number>();
2067-
2068-
walk(sourceFile);
2069-
sourceFile.nameTable = nameTable;
2066+
const nameTable = sourceFile.nameTable = createUnderscoreEscapedMap<number>();
2067+
sourceFile.forEachChild(function walk(node) {
2068+
if ((isIdentifier(node) || isStringOrNumericLiteral(node) && literalIsName(node)) && node.text) {
2069+
const text = getEscapedTextOfIdentifierOrLiteral(node);
2070+
nameTable.set(text, nameTable.get(text) === undefined ? node.pos : -1);
2071+
}
20702072

2071-
function walk(node: Node) {
2072-
switch (node.kind) {
2073-
case SyntaxKind.Identifier:
2074-
setNameTable((<Identifier>node).text, node);
2075-
break;
2076-
case SyntaxKind.StringLiteral:
2077-
case SyntaxKind.NumericLiteral:
2078-
// We want to store any numbers/strings if they were a name that could be
2079-
// related to a declaration. So, if we have 'import x = require("something")'
2080-
// then we want 'something' to be in the name table. Similarly, if we have
2081-
// "a['propname']" then we want to store "propname" in the name table.
2082-
if (isDeclarationName(node) ||
2083-
node.parent.kind === SyntaxKind.ExternalModuleReference ||
2084-
isArgumentOfElementAccessExpression(node) ||
2085-
isLiteralComputedPropertyDeclarationName(node)) {
2086-
setNameTable(getEscapedTextOfIdentifierOrLiteral((<LiteralExpression>node)), node);
2087-
}
2088-
break;
2089-
default:
2090-
forEachChild(node, walk);
2091-
if (node.jsDoc) {
2092-
for (const jsDoc of node.jsDoc) {
2093-
forEachChild(jsDoc, walk);
2094-
}
2095-
}
2073+
forEachChild(node, walk);
2074+
if (node.jsDoc) {
2075+
for (const jsDoc of node.jsDoc) {
2076+
forEachChild(jsDoc, walk);
2077+
}
20962078
}
2097-
}
2079+
});
2080+
}
20982081

2099-
function setNameTable(text: __String, node: ts.Node): void {
2100-
nameTable.set(text, nameTable.get(text) === undefined ? node.pos : -1);
2101-
}
2082+
/**
2083+
* We want to store any numbers/strings if they were a name that could be
2084+
* related to a declaration. So, if we have 'import x = require("something")'
2085+
* then we want 'something' to be in the name table. Similarly, if we have
2086+
* "a['propname']" then we want to store "propname" in the name table.
2087+
*/
2088+
function literalIsName(node: ts.StringLiteral | ts.NumericLiteral): boolean {
2089+
return isDeclarationName(node) ||
2090+
node.parent.kind === SyntaxKind.ExternalModuleReference ||
2091+
isArgumentOfElementAccessExpression(node) ||
2092+
isLiteralComputedPropertyDeclarationName(node);
21022093
}
21032094

21042095
function isObjectLiteralElement(node: Node): node is ObjectLiteralElement {

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.