Skip to content

Commit 32d0421

Browse files
author
Andy Hanson
committed
Merge branch 'master' into undefinedzilla
2 parents 03d284d + 4dc4b8d commit 32d0421

16 files changed

+225
-26
lines changed

scripts/buildProtocol.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ function writeProtocolFile(outputFile: string, protocolTs: string, typeScriptSer
178178
ts.sys.writeFile(outputFile, protocolDts);
179179

180180
if (diagnostics.length) {
181-
const flattenedDiagnostics = diagnostics.map(d => `${ts.flattenDiagnosticMessageText(d.messageText, "\n")} at ${d.file!.fileName} line ${d.start!}`).join("\n");
181+
const flattenedDiagnostics = diagnostics.map(d => `${ts.flattenDiagnosticMessageText(d.messageText, "\n")} at ${d.file ? d.file.fileName : "<unknown>"} line ${d.start}`).join("\n");
182182
throw new Error(`Unexpected errors during sanity check: ${flattenedDiagnostics}`);
183183
}
184184
}

src/compiler/checker.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,16 @@ namespace ts {
325325
return diagnostics;
326326
}
327327
},
328+
329+
runWithCancellationToken: (token, callback) => {
330+
try {
331+
cancellationToken = token;
332+
return callback(checker);
333+
}
334+
finally {
335+
cancellationToken = undefined;
336+
}
337+
}
328338
};
329339

330340
const tupleTypes: GenericType[] = [];
@@ -2130,14 +2140,22 @@ namespace ts {
21302140
return (symbol.flags & meaning) || dontResolveAlias ? symbol : resolveAlias(symbol);
21312141
}
21322142

2143+
/**
2144+
* For prototype-property methods like `A.prototype.m = function () ...`, try to resolve names in the scope of `A` too.
2145+
* Note that prototype-property assignment to locations outside the current file (eg globals) doesn't work, so
2146+
* name resolution won't work either.
2147+
*/
21332148
function resolveEntityNameFromJSPrototype(name: Identifier, meaning: SymbolFlags) {
21342149
if (isJSDocTypeReference(name.parent) && isJSDocTag(name.parent.parent.parent)) {
21352150
const host = getJSDocHost(name.parent.parent.parent as JSDocTag);
21362151
if (isExpressionStatement(host) &&
21372152
isBinaryExpression(host.expression) &&
21382153
getSpecialPropertyAssignmentKind(host.expression) === SpecialPropertyAssignmentKind.PrototypeProperty) {
2139-
const secondaryLocation = getSymbolOfNode(host.expression.left)!.parent!.valueDeclaration;
2140-
return resolveName(secondaryLocation, name.escapedText, meaning, /*nameNotFoundMessage*/ undefined, name, /*isUse*/ true);
2154+
const symbol = getSymbolOfNode(host.expression.left);
2155+
if (symbol) {
2156+
const secondaryLocation = symbol.parent!.valueDeclaration;
2157+
return resolveName(secondaryLocation, name.escapedText, meaning, /*nameNotFoundMessage*/ undefined, name, /*isUse*/ true);
2158+
}
21412159
}
21422160
}
21432161
}
@@ -3000,6 +3018,9 @@ namespace ts {
30003018
}
30013019

30023020
function typeToTypeNodeHelper(type: Type, context: NodeBuilderContext): TypeNode | undefined {
3021+
if (cancellationToken && cancellationToken.throwIfCancellationRequested) {
3022+
cancellationToken.throwIfCancellationRequested();
3023+
}
30033024
const inTypeAlias = context.flags & NodeBuilderFlags.InTypeAlias;
30043025
context.flags &= ~NodeBuilderFlags.InTypeAlias;
30053026

src/compiler/emitter.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1441,9 +1441,9 @@ namespace ts {
14411441

14421442
function emitElementAccessExpression(node: ElementAccessExpression) {
14431443
emitExpression(node.expression);
1444-
const openPos = emitTokenWithComment(SyntaxKind.OpenBracketToken, node.expression.end, writePunctuation, node);
1444+
emitTokenWithComment(SyntaxKind.OpenBracketToken, node.expression.end, writePunctuation, node);
14451445
emitExpression(node.argumentExpression);
1446-
emitTokenWithComment(SyntaxKind.CloseBracketToken, node.argumentExpression ? node.argumentExpression.end : openPos, writePunctuation, node);
1446+
emitTokenWithComment(SyntaxKind.CloseBracketToken, node.argumentExpression.end, writePunctuation, node);
14471447
}
14481448

14491449
function emitCallExpression(node: CallExpression) {

src/compiler/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3003,6 +3003,13 @@ namespace ts {
30033003
* Others are added in computeSuggestionDiagnostics.
30043004
*/
30053005
/* @internal */ getSuggestionDiagnostics(file: SourceFile): ReadonlyArray<Diagnostic>;
3006+
3007+
/**
3008+
* Depending on the operation performed, it may be appropriate to throw away the checker
3009+
* if the cancellation token is triggered. Typically, if it is used for error checking
3010+
* and the operation is cancelled, then it should be discarded, otherwise it is safe to keep.
3011+
*/
3012+
runWithCancellationToken<T>(token: CancellationToken, cb: (checker: TypeChecker) => T): T;
30063013
}
30073014

30083015
/* @internal */

src/harness/harness.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -304,14 +304,13 @@ namespace Utils {
304304
o.containsParseError = true;
305305
}
306306

307-
ts.forEach(Object.getOwnPropertyNames(n), propertyName => {
307+
for (const propertyName of Object.getOwnPropertyNames(n) as ReadonlyArray<keyof ts.SourceFile | keyof ts.Identifier>) {
308308
switch (propertyName) {
309309
case "parent":
310310
case "symbol":
311311
case "locals":
312312
case "localSymbol":
313313
case "kind":
314-
case "semanticDiagnostics":
315314
case "id":
316315
case "nodeCount":
317316
case "symbolCount":
@@ -334,7 +333,6 @@ namespace Utils {
334333
}
335334
break;
336335

337-
case "referenceDiagnostics":
338336
case "parseDiagnostics":
339337
o[propertyName] = convertDiagnostics((<any>n)[propertyName]);
340338
break;
@@ -355,9 +353,7 @@ namespace Utils {
355353
default:
356354
o[propertyName] = (<any>n)[propertyName];
357355
}
358-
359-
return undefined;
360-
});
356+
}
361357

362358
return o;
363359
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/// <reference path="..\harness.ts" />
2+
3+
namespace ts {
4+
describe("cancellableLanguageServiceOperations", () => {
5+
const file = `
6+
function foo(): void;
7+
function foo<T>(x: T): T;
8+
function foo<T>(x?: T): T | void {}
9+
foo(f);
10+
`;
11+
it("can cancel signature help mid-request", () => {
12+
verifyOperationCancelledAfter(file, 4, service => // Two calls are top-level in services, one is the root type, and the second should be for the parameter type
13+
service.getSignatureHelpItems("file.ts", file.lastIndexOf("f"))!,
14+
r => assert.exists(r.items[0])
15+
);
16+
});
17+
18+
it("can cancel find all references mid-request", () => {
19+
verifyOperationCancelledAfter(file, 3, service => // Two calls are top-level in services, one is the root type
20+
service.findReferences("file.ts", file.lastIndexOf("o"))!,
21+
r => assert.exists(r[0].definition)
22+
);
23+
});
24+
25+
it("can cancel quick info mid-request", () => {
26+
verifyOperationCancelledAfter(file, 1, service => // The LS doesn't do any top-level checks on the token for quickinfo, so the first check is within the checker
27+
service.getQuickInfoAtPosition("file.ts", file.lastIndexOf("o"))!,
28+
r => assert.exists(r.displayParts)
29+
);
30+
});
31+
32+
it("can cancel completion entry details mid-request", () => {
33+
const options: FormatCodeSettings = {
34+
indentSize: 4,
35+
tabSize: 4,
36+
newLineCharacter: "\n",
37+
convertTabsToSpaces: true,
38+
indentStyle: IndentStyle.Smart,
39+
insertSpaceAfterConstructor: false,
40+
insertSpaceAfterCommaDelimiter: true,
41+
insertSpaceAfterSemicolonInForStatements: true,
42+
insertSpaceBeforeAndAfterBinaryOperators: true,
43+
insertSpaceAfterKeywordsInControlFlowStatements: true,
44+
insertSpaceAfterFunctionKeywordForAnonymousFunctions: false,
45+
insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false,
46+
insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false,
47+
insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true,
48+
insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false,
49+
insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false,
50+
insertSpaceBeforeFunctionParenthesis: false,
51+
placeOpenBraceOnNewLineForFunctions: false,
52+
placeOpenBraceOnNewLineForControlBlocks: false,
53+
};
54+
verifyOperationCancelledAfter(file, 1, service => // The LS doesn't do any top-level checks on the token for completion entry details, so the first check is within the checker
55+
service.getCompletionEntryDetails("file.ts", file.lastIndexOf("f"), "foo", options, /*content*/ undefined, {})!,
56+
r => assert.exists(r.displayParts)
57+
);
58+
});
59+
});
60+
61+
function verifyOperationCancelledAfter<T>(content: string, cancelAfter: number, operation: (service: LanguageService) => T, validator: (arg: T) => void) {
62+
let checks = 0;
63+
const token: HostCancellationToken = {
64+
isCancellationRequested() {
65+
checks++;
66+
const result = checks >= cancelAfter;
67+
if (result) {
68+
checks = -Infinity; // Cancel just once, then disable cancellation, effectively
69+
}
70+
return result;
71+
}
72+
};
73+
const adapter = new Harness.LanguageService.NativeLanguageServiceAdapter(token);
74+
const host = adapter.getHost();
75+
host.addScript("file.ts", content, /*isRootFile*/ true);
76+
const service = adapter.getLanguageService();
77+
assertCancelled(() => operation(service));
78+
validator(operation(service));
79+
}
80+
81+
/**
82+
* We don't just use `assert.throws` because it doesn't validate instances for thrown objects which do not inherit from `Error`
83+
*/
84+
function assertCancelled(cb: () => void) {
85+
let caught: any;
86+
try {
87+
cb();
88+
}
89+
catch (e) {
90+
caught = e;
91+
}
92+
assert.exists(caught, "Expected operation to be cancelled, but was not");
93+
assert.instanceOf(caught, OperationCanceledException);
94+
}
95+
}

src/services/completions.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,7 @@ namespace ts.Completions {
534534
formatContext: formatting.FormatContext,
535535
getCanonicalFileName: GetCanonicalFileName,
536536
preferences: UserPreferences,
537+
cancellationToken: CancellationToken,
537538
): CompletionEntryDetails | undefined {
538539
const typeChecker = program.getTypeChecker();
539540
const compilerOptions = program.getCompilerOptions();
@@ -544,7 +545,7 @@ namespace ts.Completions {
544545
const stringLiteralCompletions = !contextToken || !isStringLiteralLike(contextToken)
545546
? undefined
546547
: getStringLiteralCompletionEntries(sourceFile, contextToken, position, typeChecker, compilerOptions, host);
547-
return stringLiteralCompletions && stringLiteralCompletionDetails(name, contextToken!, stringLiteralCompletions, sourceFile, typeChecker); // TODO: GH#18217
548+
return stringLiteralCompletions && stringLiteralCompletionDetails(name, contextToken!, stringLiteralCompletions, sourceFile, typeChecker, cancellationToken); // TODO: GH#18217
548549
}
549550

550551
// Compute all the completion symbols again.
@@ -566,28 +567,31 @@ namespace ts.Completions {
566567
case "symbol": {
567568
const { symbol, location, symbolToOriginInfoMap, previousToken } = symbolCompletion;
568569
const { codeActions, sourceDisplay } = getCompletionEntryCodeActionsAndSourceDisplay(symbolToOriginInfoMap, symbol, program, typeChecker, host, compilerOptions, sourceFile, previousToken, formatContext, getCanonicalFileName, program.getSourceFiles(), preferences);
569-
return createCompletionDetailsForSymbol(symbol, typeChecker, sourceFile, location!, codeActions, sourceDisplay); // TODO: GH#18217
570+
return createCompletionDetailsForSymbol(symbol, typeChecker, sourceFile, location!, cancellationToken, codeActions, sourceDisplay); // TODO: GH#18217
570571
}
571572
case "none":
572573
// Didn't find a symbol with this name. See if we can find a keyword instead.
573574
return allKeywordsCompletions().some(c => c.name === name) ? createCompletionDetails(name, ScriptElementKindModifier.none, ScriptElementKind.keyword, [displayPart(name, SymbolDisplayPartKind.keyword)]) : undefined;
574575
}
575576
}
576577

577-
function createCompletionDetailsForSymbol(symbol: Symbol, checker: TypeChecker, sourceFile: SourceFile, location: Node, codeActions?: CodeAction[], sourceDisplay?: SymbolDisplayPart[]): CompletionEntryDetails {
578-
const { displayParts, documentation, symbolKind, tags } = SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(checker, symbol, sourceFile, location, location, SemanticMeaning.All);
578+
function createCompletionDetailsForSymbol(symbol: Symbol, checker: TypeChecker, sourceFile: SourceFile, location: Node, cancellationToken: CancellationToken, codeActions?: CodeAction[], sourceDisplay?: SymbolDisplayPart[]): CompletionEntryDetails {
579+
const { displayParts, documentation, symbolKind, tags } =
580+
checker.runWithCancellationToken(cancellationToken, checker =>
581+
SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(checker, symbol, sourceFile, location, location, SemanticMeaning.All)
582+
);
579583
return createCompletionDetails(symbol.name, SymbolDisplay.getSymbolModifiers(symbol), symbolKind, displayParts, documentation, tags, codeActions, sourceDisplay);
580584
}
581585

582-
function stringLiteralCompletionDetails(name: string, location: Node, completion: StringLiteralCompletion, sourceFile: SourceFile, checker: TypeChecker): CompletionEntryDetails | undefined {
586+
function stringLiteralCompletionDetails(name: string, location: Node, completion: StringLiteralCompletion, sourceFile: SourceFile, checker: TypeChecker, cancellationToken: CancellationToken): CompletionEntryDetails | undefined {
583587
switch (completion.kind) {
584588
case StringLiteralCompletionKind.Paths: {
585589
const match = find(completion.paths, p => p.name === name);
586590
return match && createCompletionDetails(name, ScriptElementKindModifier.none, match.kind, [textPart(name)]);
587591
}
588592
case StringLiteralCompletionKind.Properties: {
589593
const match = find(completion.symbols, s => s.name === name);
590-
return match && createCompletionDetailsForSymbol(match, checker, sourceFile, location);
594+
return match && createCompletionDetailsForSymbol(match, checker, sourceFile, location, cancellationToken);
591595
}
592596
case StringLiteralCompletionKind.Types:
593597
return find(completion.types, t => t.value === name) ? createCompletionDetails(name, ScriptElementKindModifier.none, ScriptElementKind.typeElement, [textPart(name)]) : undefined;

src/services/findAllReferences.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@ namespace ts.FindAllReferences {
4545
const checker = program.getTypeChecker();
4646
return !referencedSymbols || !referencedSymbols.length ? undefined : mapDefined<SymbolAndEntries, ReferencedSymbol>(referencedSymbols, ({ definition, references }) =>
4747
// Only include referenced symbols that have a valid definition.
48-
definition && { definition: definitionToReferencedSymbolDefinitionInfo(definition, checker, node), references: references.map(toReferenceEntry) });
48+
definition && {
49+
definition: checker.runWithCancellationToken(cancellationToken, checker => definitionToReferencedSymbolDefinitionInfo(definition, checker, node)),
50+
references: references.map(toReferenceEntry)
51+
});
4952
}
5053

5154
export function getImplementationsAtPosition(program: Program, cancellationToken: CancellationToken, sourceFiles: ReadonlyArray<SourceFile>, sourceFile: SourceFile, position: number): ImplementationLocation[] | undefined {

src/services/services.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -553,7 +553,6 @@ namespace ts {
553553
public typeReferenceDirectives: FileReference[];
554554

555555
public syntacticDiagnostics: Diagnostic[];
556-
public referenceDiagnostics: Diagnostic[];
557556
public parseDiagnostics: Diagnostic[];
558557
public bindDiagnostics: Diagnostic[];
559558

@@ -1432,7 +1431,9 @@ namespace ts {
14321431
host,
14331432
(formattingOptions && formatting.getFormatContext(formattingOptions))!, // TODO: GH#18217
14341433
getCanonicalFileName,
1435-
preferences);
1434+
preferences,
1435+
cancellationToken,
1436+
);
14361437
}
14371438

14381439
function getCompletionEntrySymbol(fileName: string, position: number, name: string, source?: string): Symbol | undefined {
@@ -1473,7 +1474,7 @@ namespace ts {
14731474
kind: ScriptElementKind.unknown,
14741475
kindModifiers: ScriptElementKindModifier.none,
14751476
textSpan: createTextSpanFromNode(node, sourceFile),
1476-
displayParts: typeToDisplayParts(typeChecker, type, getContainerNode(node)),
1477+
displayParts: typeChecker.runWithCancellationToken(cancellationToken, typeChecker => typeToDisplayParts(typeChecker, type, getContainerNode(node))),
14771478
documentation: type.symbol ? type.symbol.getDocumentationComment(typeChecker) : undefined,
14781479
tags: type.symbol ? type.symbol.getJsDocTags() : undefined
14791480
};
@@ -1482,7 +1483,9 @@ namespace ts {
14821483
return undefined;
14831484
}
14841485

1485-
const { symbolKind, displayParts, documentation, tags } = SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker, symbol, sourceFile, getContainerNode(node), node);
1486+
const { symbolKind, displayParts, documentation, tags } = typeChecker.runWithCancellationToken(cancellationToken, typeChecker =>
1487+
SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker, symbol, sourceFile, getContainerNode(node), node)
1488+
);
14861489
return {
14871490
kind: symbolKind,
14881491
kindModifiers: SymbolDisplay.getSymbolModifiers(symbol),

0 commit comments

Comments
 (0)