Skip to content

Commit 01ea0a6

Browse files
committed
Merge branch 'master' into import-types-completions
2 parents 895be38 + 92dfde0 commit 01ea0a6

18 files changed

+607
-302
lines changed

Jakefile.js

+1
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ var harnessSources = harnessCoreSources.concat([
154154
"transform.ts",
155155
"customTransforms.ts",
156156
"programMissingFiles.ts",
157+
"programNoParseFalsyFileNames.ts",
157158
"symbolWalker.ts",
158159
"languageService.ts",
159160
"publicApi.ts",

src/compiler/checker.ts

+9-6
Original file line numberDiff line numberDiff line change
@@ -8466,15 +8466,19 @@ namespace ts {
84668466
return result;
84678467
}
84688468

8469-
function getTopConditionalType(node: Node): ConditionalTypeNode {
8470-
let result: ConditionalTypeNode;
8469+
function isPossiblyReferencedInConditionalType(tp: TypeParameter, node: Node) {
8470+
if (isTypeParameterPossiblyReferenced(tp, node)) {
8471+
return true;
8472+
}
84718473
while (node) {
84728474
if (node.kind === SyntaxKind.ConditionalType) {
8473-
result = <ConditionalTypeNode>node;
8475+
if (isTypeParameterPossiblyReferenced(tp, (<ConditionalTypeNode>node).extendsType)) {
8476+
return true;
8477+
}
84748478
}
84758479
node = node.parent;
84768480
}
8477-
return result;
8481+
return false;
84788482
}
84798483

84808484
function getTypeFromConditionalTypeNode(node: ConditionalTypeNode): Type {
@@ -8483,8 +8487,7 @@ namespace ts {
84838487
const checkType = getTypeFromTypeNode(node.checkType);
84848488
const aliasTypeArguments = getAliasTypeArgumentsForTypeNode(node);
84858489
const allOuterTypeParameters = getOuterTypeParameters(node, /*includeThisTypes*/ true);
8486-
const topNode = getTopConditionalType(node);
8487-
const outerTypeParameters = aliasTypeArguments ? allOuterTypeParameters : filter(allOuterTypeParameters, tp => isTypeParameterPossiblyReferenced(tp, topNode));
8490+
const outerTypeParameters = aliasTypeArguments ? allOuterTypeParameters : filter(allOuterTypeParameters, tp => isPossiblyReferencedInConditionalType(tp, node));
84888491
const root: ConditionalRoot = {
84898492
node,
84908493
checkType,

src/compiler/program.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -611,8 +611,9 @@ namespace ts {
611611
if (!skipDefaultLib) {
612612
// If '--lib' is not specified, include default library file according to '--target'
613613
// otherwise, using options specified in '--lib' instead of '--target' default library file
614-
if (!options.lib) {
615-
processRootFile(getDefaultLibraryFileName(), /*isDefaultLib*/ true);
614+
const defaultLibraryFileName = getDefaultLibraryFileName();
615+
if (!options.lib && defaultLibraryFileName) {
616+
processRootFile(defaultLibraryFileName, /*isDefaultLib*/ true);
616617
}
617618
else {
618619
forEach(options.lib, libFileName => {
@@ -1117,7 +1118,7 @@ namespace ts {
11171118
// otherwise, using options specified in '--lib' instead of '--target' default library file
11181119
const equalityComparer = host.useCaseSensitiveFileNames() ? equateStringsCaseSensitive : equateStringsCaseInsensitive;
11191120
if (!options.lib) {
1120-
return equalityComparer(file.fileName, getDefaultLibraryFileName());
1121+
return equalityComparer(file.fileName, getDefaultLibraryFileName());
11211122
}
11221123
else {
11231124
return forEach(options.lib, libFileName => equalityComparer(file.fileName, combinePaths(defaultLibraryPath, libFileName)));

src/harness/tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@
137137
"./unittests/telemetry.ts",
138138
"./unittests/languageService.ts",
139139
"./unittests/programMissingFiles.ts",
140+
"./unittests/programNoParseFalsyFileNames.ts",
140141
"./unittests/publicApi.ts",
141142
"./unittests/hostNewLineSupport.ts"
142143
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/// <reference path="..\harness.ts" />
2+
namespace ts {
3+
describe("programNoParseFalsyFileNames", () => {
4+
let program: Program;
5+
6+
beforeEach(() => {
7+
const testSource = `
8+
class Foo extends HTMLElement {
9+
bar: string = 'baz';
10+
}`;
11+
12+
const host: CompilerHost = {
13+
getSourceFile: (fileName: string, languageVersion: ScriptTarget, _onError?: (message: string) => void) => {
14+
return fileName === "test.ts" ? createSourceFile(fileName, testSource, languageVersion) : undefined;
15+
},
16+
getDefaultLibFileName: () => "",
17+
writeFile: (_fileName, _content) => { throw new Error("unsupported"); },
18+
getCurrentDirectory: () => sys.getCurrentDirectory(),
19+
getCanonicalFileName: fileName => sys.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase(),
20+
getNewLine: () => sys.newLine,
21+
useCaseSensitiveFileNames: () => sys.useCaseSensitiveFileNames,
22+
fileExists: fileName => fileName === "test.ts",
23+
readFile: fileName => fileName === "test.ts" ? testSource : undefined,
24+
resolveModuleNames: (_moduleNames: string[], _containingFile: string) => { throw new Error("unsupported"); },
25+
getDirectories: _path => { throw new Error("unsupported"); },
26+
};
27+
28+
program = createProgram(["test.ts"], { module: ModuleKind.ES2015 }, host);
29+
});
30+
31+
it("should not have missing file paths", () => {
32+
assert(program.getSourceFiles().length === 1, "expected 'getSourceFiles' length to be 1");
33+
assert(program.getMissingFilePaths().length === 0, "expected 'getMissingFilePaths' length to be 0");
34+
assert(program.getFileProcessingDiagnostics().getDiagnostics().length === 0, "expected 'getFileProcessingDiagnostics' length to be 0");
35+
});
36+
});
37+
}

src/services/completions.ts

+58-21
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
namespace ts.Completions {
55
export type Log = (message: string) => void;
66

7-
type SymbolOriginInfo = { type: "this-type" } | SymbolOriginInfoExport;
7+
type SymbolOriginInfo = { type: "this-type" } | { type: "symbol-member" } | SymbolOriginInfoExport;
88
interface SymbolOriginInfoExport {
99
type: "export";
1010
moduleSymbol: Symbol;
@@ -83,7 +83,7 @@ namespace ts.Completions {
8383
}
8484
case StringLiteralCompletionKind.Types: {
8585
const entries = completion.types.map(type => ({ name: type.value, kindModifiers: ScriptElementKindModifier.none, kind: ScriptElementKind.typeElement, sortText: "0" }));
86-
return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, entries };
86+
return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: completion.isNewIdentifier, entries };
8787
}
8888
default:
8989
return Debug.assertNever(completion);
@@ -206,11 +206,13 @@ namespace ts.Completions {
206206
if (origin && origin.type === "this-type") {
207207
insertText = needsConvertPropertyAccess ? `this[${quote(name, preferences)}]` : `this.${name}`;
208208
}
209-
else if (needsConvertPropertyAccess) {
210-
insertText = `[${quote(name, preferences)}]`;
209+
// We should only have needsConvertPropertyAccess if there's a property access to convert. But see #21790.
210+
// Somehow there was a global with a non-identifier name. Hopefully someone will complain about getting a "foo bar" global completion and provide a repro.
211+
else if ((origin && origin.type === "symbol-member" || needsConvertPropertyAccess) && propertyAccessToConvert) {
212+
insertText = needsConvertPropertyAccess ? `[${quote(name, preferences)}]` : `[${name}]`;
211213
const dot = findChildOfKind(propertyAccessToConvert!, SyntaxKind.DotToken, sourceFile)!;
212214
// If the text after the '.' starts with this name, write over it. Else, add new text.
213-
const end = startsWith(name, propertyAccessToConvert!.name.text) ? propertyAccessToConvert!.name.end : dot.end;
215+
const end = startsWith(name, propertyAccessToConvert.name.text) ? propertyAccessToConvert.name.end : dot.end;
214216
replacementSpan = createTextSpanFromBounds(dot.getStart(sourceFile), end);
215217
}
216218

@@ -358,16 +360,18 @@ namespace ts.Completions {
358360
readonly symbols: ReadonlyArray<Symbol>;
359361
readonly hasIndexSignature: boolean;
360362
}
361-
type StringLiteralCompletion =
362-
| { readonly kind: StringLiteralCompletionKind.Paths, readonly paths: ReadonlyArray<PathCompletions.PathCompletion> }
363-
| StringLiteralCompletionsFromProperties
364-
| { readonly kind: StringLiteralCompletionKind.Types, readonly types: ReadonlyArray<StringLiteralType> };
363+
interface StringLiteralCompletionsFromTypes {
364+
readonly kind: StringLiteralCompletionKind.Types;
365+
readonly types: ReadonlyArray<StringLiteralType>;
366+
readonly isNewIdentifier: boolean;
367+
}
368+
type StringLiteralCompletion = { readonly kind: StringLiteralCompletionKind.Paths, readonly paths: ReadonlyArray<PathCompletions.PathCompletion> } | StringLiteralCompletionsFromProperties | StringLiteralCompletionsFromTypes;
365369
function getStringLiteralCompletionEntries(sourceFile: SourceFile, node: StringLiteralLike, position: number, typeChecker: TypeChecker, compilerOptions: CompilerOptions, host: LanguageServiceHost): StringLiteralCompletion | undefined {
366370
switch (node.parent.kind) {
367371
case SyntaxKind.LiteralType:
368372
switch (node.parent.parent.kind) {
369373
case SyntaxKind.TypeReference:
370-
return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(node.parent as LiteralTypeNode), typeChecker) };
374+
return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(node.parent as LiteralTypeNode), typeChecker), isNewIdentifier: false };
371375
case SyntaxKind.IndexedAccessType:
372376
// Get all apparent property names
373377
// i.e. interface Foo {
@@ -376,6 +380,8 @@ namespace ts.Completions {
376380
// }
377381
// let x: Foo["/*completion position*/"]
378382
return stringLiteralCompletionsFromProperties(typeChecker.getTypeFromTypeNode((node.parent.parent as IndexedAccessTypeNode).objectType));
383+
case SyntaxKind.ImportTypeNode:
384+
return { kind: StringLiteralCompletionKind.Paths, paths: PathCompletions.getStringLiteralCompletionsFromModuleNames(sourceFile, node, compilerOptions, host, typeChecker) };
379385
default:
380386
return undefined;
381387
}
@@ -419,13 +425,7 @@ namespace ts.Completions {
419425
// Get string literal completions from specialized signatures of the target
420426
// i.e. declare function f(a: 'A');
421427
// f("/*completion position*/")
422-
if (argumentInfo) {
423-
const candidates: Signature[] = [];
424-
typeChecker.getResolvedSignature(argumentInfo.invocation, candidates, argumentInfo.argumentCount);
425-
const uniques = createMap<true>();
426-
return { kind: StringLiteralCompletionKind.Types, types: flatMap(candidates, candidate => getStringLiteralTypes(typeChecker.getParameterType(candidate, argumentInfo.argumentIndex), typeChecker, uniques)) };
427-
}
428-
return fromContextualType();
428+
return argumentInfo ? getStringLiteralCompletionsFromSignature(argumentInfo, typeChecker) : fromContextualType();
429429
}
430430
// falls through (is `require("")` or `import("")`)
431431

@@ -447,10 +447,26 @@ namespace ts.Completions {
447447
function fromContextualType(): StringLiteralCompletion {
448448
// Get completion for string literal from string literal type
449449
// i.e. var x: "hi" | "hello" = "/*completion position*/"
450-
return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(getContextualTypeFromParent(node, typeChecker), typeChecker) };
450+
return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(getContextualTypeFromParent(node, typeChecker), typeChecker), isNewIdentifier: false };
451451
}
452452
}
453453

454+
function getStringLiteralCompletionsFromSignature(argumentInfo: SignatureHelp.ArgumentListInfo, checker: TypeChecker): StringLiteralCompletionsFromTypes {
455+
let isNewIdentifier = false;
456+
457+
const uniques = createMap<true>();
458+
const candidates: Signature[] = [];
459+
checker.getResolvedSignature(argumentInfo.invocation, candidates, argumentInfo.argumentCount);
460+
const types = flatMap(candidates, candidate => {
461+
if (!candidate.hasRestParameter && argumentInfo.argumentCount > candidate.parameters.length) return;
462+
const type = checker.getParameterType(candidate, argumentInfo.argumentIndex);
463+
isNewIdentifier = isNewIdentifier || !!(type.flags & TypeFlags.String);
464+
return getStringLiteralTypes(type, checker, uniques);
465+
});
466+
467+
return { kind: StringLiteralCompletionKind.Types, types, isNewIdentifier };
468+
}
469+
454470
function stringLiteralCompletionsFromProperties(type: Type | undefined): StringLiteralCompletionsFromProperties | undefined {
455471
return type && { kind: StringLiteralCompletionKind.Properties, symbols: type.getApparentProperties(), hasIndexSignature: hasIndexSignature(type) };
456472
}
@@ -1051,13 +1067,34 @@ namespace ts.Completions {
10511067
}
10521068
else {
10531069
for (const symbol of type.getApparentProperties()) {
1054-
if (typeChecker.isValidPropertyAccessForCompletions(node.kind === SyntaxKind.ImportTypeNode ? <ImportTypeNode>node : <PropertyAccessExpression>(node.parent), type, symbol)) {
1055-
symbols.push(symbol);
1070+
if (typeChecker.isValidPropertyAccessForCompletions(node.kind === SyntaxKind.ImportTypeNode ? <ImportTypeNode>node : <PropertyAccessExpression>node.parent, type, symbol)) {
1071+
addPropertySymbol(symbol);
10561072
}
10571073
}
10581074
}
10591075
}
10601076

1077+
function addPropertySymbol(symbol: Symbol) {
1078+
// If this is e.g. [Symbol.iterator], add a completion for `Symbol`.
1079+
const symbolSymbol = firstDefined(symbol.declarations, decl => {
1080+
const name = getNameOfDeclaration(decl);
1081+
const leftName = name.kind === SyntaxKind.ComputedPropertyName ? getLeftMostName(name.expression) : undefined;
1082+
return leftName && typeChecker.getSymbolAtLocation(leftName);
1083+
});
1084+
if (symbolSymbol) {
1085+
symbols.push(symbolSymbol);
1086+
symbolToOriginInfoMap[getSymbolId(symbolSymbol)] = { type: "symbol-member" };
1087+
}
1088+
else {
1089+
symbols.push(symbol);
1090+
}
1091+
}
1092+
1093+
/** Given 'a.b.c', returns 'a'. */
1094+
function getLeftMostName(e: Expression): Identifier | undefined {
1095+
return isIdentifier(e) ? e : isPropertyAccessExpression(e) ? getLeftMostName(e.expression) : undefined;
1096+
}
1097+
10611098
function tryGetGlobalSymbols(): boolean {
10621099
const result: GlobalsSearch = tryGetObjectLikeCompletionSymbols()
10631100
|| tryGetImportOrExportClauseCompletionSymbols()
@@ -2049,7 +2086,7 @@ namespace ts.Completions {
20492086
// TODO: GH#18169
20502087
return { name: JSON.stringify(name), needsConvertPropertyAccess: false };
20512088
case CompletionKind.PropertyAccess:
2052-
case CompletionKind.Global:
2089+
case CompletionKind.Global: // For a 'this.' completion it will be in a global context, but may have a non-identifier name.
20532090
// Don't add a completion for a name starting with a space. See https://github.com/Microsoft/TypeScript/pull/20547
20542091
return name.charCodeAt(0) === CharacterCodes.space ? undefined : { name, needsConvertPropertyAccess: true };
20552092
case CompletionKind.None:

0 commit comments

Comments
 (0)