Merge branch 'master' into release-3.6

This commit is contained in:
Daniel Rosenwasser
2019-07-16 11:42:10 -07:00
12 changed files with 193 additions and 20 deletions
+9 -4
View File
@@ -20573,10 +20573,15 @@ namespace ts {
}
propType = getConstraintForLocation(getTypeOfSymbol(prop), node);
}
return getFlowTypeOfAccessExpression(node, prop, propType, right);
}
function getFlowTypeOfAccessExpression(node: ElementAccessExpression | PropertyAccessExpression | QualifiedName, prop: Symbol | undefined, propType: Type, errorNode: Node) {
// Only compute control flow type if this is a property access expression that isn't an
// assignment target, and the referenced property was declared as a variable, property,
// accessor, or optional method.
if (node.kind !== SyntaxKind.PropertyAccessExpression ||
const assignmentKind = getAssignmentTargetKind(node);
if (node.kind !== SyntaxKind.ElementAccessExpression && node.kind !== SyntaxKind.PropertyAccessExpression ||
assignmentKind === AssignmentKind.Definite ||
prop && !(prop.flags & (SymbolFlags.Variable | SymbolFlags.Property | SymbolFlags.Accessor)) && !(prop.flags & SymbolFlags.Method && propType.flags & TypeFlags.Union)) {
return propType;
@@ -20586,7 +20591,7 @@ namespace ts {
// and if we are in a constructor of the same class as the property declaration, assume that
// the property is uninitialized at the top of the control flow.
let assumeUninitialized = false;
if (strictNullChecks && strictPropertyInitialization && left.kind === SyntaxKind.ThisKeyword) {
if (strictNullChecks && strictPropertyInitialization && node.expression.kind === SyntaxKind.ThisKeyword) {
const declaration = prop && prop.valueDeclaration;
if (declaration && isInstancePropertyWithoutInitializer(declaration)) {
const flowContainer = getControlFlowContainer(node);
@@ -20603,7 +20608,7 @@ namespace ts {
}
const flowType = getFlowTypeOfReference(node, propType, assumeUninitialized ? getOptionalType(propType) : propType);
if (assumeUninitialized && !(getFalsyFlags(propType) & TypeFlags.Undefined) && getFalsyFlags(flowType) & TypeFlags.Undefined) {
error(right, Diagnostics.Property_0_is_used_before_being_assigned, symbolToString(prop!)); // TODO: GH#18217
error(errorNode, Diagnostics.Property_0_is_used_before_being_assigned, symbolToString(prop!)); // TODO: GH#18217
// Return the declared type to reduce follow-on errors
return propType;
}
@@ -20960,7 +20965,7 @@ namespace ts {
AccessFlags.Writing | (isGenericObjectType(objectType) && !isThisTypeParameter(objectType) ? AccessFlags.NoIndexSignatures : 0) :
AccessFlags.None;
const indexedAccessType = getIndexedAccessTypeOrUndefined(objectType, effectiveIndexType, node, accessFlags) || errorType;
return checkIndexedAccessIndexType(indexedAccessType, node);
return checkIndexedAccessIndexType(getFlowTypeOfAccessExpression(node, indexedAccessType.symbol, indexedAccessType, indexExpression), node);
}
function checkThatExpressionIsProperSymbolReference(expression: Expression, expressionType: Type, reportError: boolean): boolean {
+6 -3
View File
@@ -865,7 +865,7 @@ namespace FourSlash {
ts.zipWith(actual, expected, (completion, expectedCompletion, index) => {
const name = typeof expectedCompletion === "string" ? expectedCompletion : expectedCompletion.name;
if (completion.name !== name) {
this.raiseError(`${marker ? JSON.stringify(marker) : "" } Expected completion at index ${index} to be ${name}, got ${completion.name}`);
this.raiseError(`${marker ? JSON.stringify(marker) : ""} Expected completion at index ${index} to be ${name}, got ${completion.name}`);
}
this.verifyCompletionEntry(completion, expectedCompletion);
});
@@ -3759,7 +3759,7 @@ namespace FourSlashInterface {
}
export class Plugins {
constructor (private state: FourSlash.TestState) {
constructor(private state: FourSlash.TestState) {
}
public configurePlugin(pluginName: string, configuration: any): void {
@@ -4582,7 +4582,7 @@ namespace FourSlashInterface {
export const keywords: ReadonlyArray<ExpectedCompletionEntryObject> = keywordsWithUndefined.filter(k => k.name !== "undefined");
export const typeKeywords: ReadonlyArray<ExpectedCompletionEntryObject> =
["false", "null", "true", "void", "any", "boolean", "keyof", "never", "number", "object", "string", "symbol", "undefined", "unique", "unknown", "bigint"].map(keywordEntry);
["false", "null", "true", "void", "any", "boolean", "keyof", "never", "readonly", "number", "object", "string", "symbol", "undefined", "unique", "unknown", "bigint"].map(keywordEntry);
const globalTypeDecls: ReadonlyArray<ExpectedCompletionEntryObject> = [
interfaceEntry("Symbol"),
@@ -4698,6 +4698,9 @@ namespace FourSlashInterface {
];
}
export const typeAssertionKeywords: ReadonlyArray<ExpectedCompletionEntry> =
globalTypesPlus([keywordEntry("const")]);
function getInJsKeywords(keywords: ReadonlyArray<ExpectedCompletionEntryObject>): ReadonlyArray<ExpectedCompletionEntryObject> {
return keywords.filter(keyword => {
switch (keyword.name) {
+1 -1
View File
@@ -30,7 +30,7 @@ interface Array<T> {}`
return combinePaths(getDirectoryPath(libFile.path), "tsc.js");
}
interface TestServerHostCreationParameters {
export interface TestServerHostCreationParameters {
useCaseSensitiveFileNames?: boolean;
executingFilePath?: string;
currentDirectory?: string;
+21 -9
View File
@@ -38,6 +38,7 @@ namespace ts.Completions {
InterfaceElementKeywords, // Keywords inside interface body
ConstructorParameterKeywords, // Keywords at constructor parameter
FunctionLikeBodyKeywords, // Keywords at function like body
TypeAssertionKeywords,
TypeKeywords,
Last = TypeKeywords
}
@@ -441,7 +442,7 @@ namespace ts.Completions {
(symbol.escapedName === InternalSymbolName.ExportEquals))
// Name of "export default foo;" is "foo". Name of "export default 0" is the filename converted to camelCase.
? firstDefined(symbol.declarations, d => isExportAssignment(d) && isIdentifier(d.expression) ? d.expression.text : undefined)
|| codefix.moduleSymbolToValidIdentifier(origin.moduleSymbol, target)
|| codefix.moduleSymbolToValidIdentifier(origin.moduleSymbol, target)
: symbol.name;
}
@@ -632,9 +633,9 @@ namespace ts.Completions {
// At `,`, treat this as the next argument after the comma.
? checker.getContextualTypeForArgumentAtIndex(argInfo.invocation, argInfo.argumentIndex + (previousToken.kind === SyntaxKind.CommaToken ? 1 : 0))
: isEqualityOperatorKind(previousToken.kind) && isBinaryExpression(parent) && isEqualityOperatorKind(parent.operatorToken.kind)
// completion at `x ===/**/` should be for the right side
? checker.getTypeAtLocation(parent.left)
: checker.getContextualType(previousToken as Expression);
// completion at `x ===/**/` should be for the right side
? checker.getTypeAtLocation(parent.left)
: checker.getContextualType(previousToken as Expression);
}
}
@@ -1182,7 +1183,11 @@ namespace ts.Completions {
function filterGlobalCompletion(symbols: Symbol[]): void {
const isTypeOnly = isTypeOnlyCompletion();
const allowTypes = isTypeOnly || !isContextTokenValueLocation(contextToken) && isPossiblyTypeArgumentPosition(contextToken, sourceFile, typeChecker);
if (isTypeOnly) keywordFilters = KeywordCompletionFilters.TypeKeywords;
if (isTypeOnly) {
keywordFilters = isTypeAssertion()
? KeywordCompletionFilters.TypeAssertionKeywords
: KeywordCompletionFilters.TypeKeywords;
}
filterMutate(symbols, symbol => {
if (!isSourceFile(location)) {
@@ -1212,6 +1217,10 @@ namespace ts.Completions {
});
}
function isTypeAssertion(): boolean {
return isAssertionExpression(contextToken.parent);
}
function isTypeOnlyCompletion(): boolean {
return insideJsDocTagTypeExpression || !isContextTokenValueLocation(contextToken) && (isPartOfTypeNode(location) || isContextTokenTypeLocation(contextToken));
}
@@ -1240,7 +1249,8 @@ namespace ts.Completions {
return parentKind === SyntaxKind.AsExpression;
case SyntaxKind.LessThanToken:
return parentKind === SyntaxKind.TypeReference;
return parentKind === SyntaxKind.TypeReference ||
parentKind === SyntaxKind.TypeAssertionExpression;
case SyntaxKind.ExtendsKeyword:
return parentKind === SyntaxKind.TypeParameter;
@@ -1516,7 +1526,7 @@ namespace ts.Completions {
// 3. at the end of a regular expression (due to trailing flags like '/foo/g').
return (isRegularExpressionLiteral(contextToken) || isStringTextContainingNode(contextToken)) && (
rangeContainsPositionExclusive(createTextRangeFromSpan(createTextSpanFromNode(contextToken)), position) ||
position === contextToken.end && (!!contextToken.isUnterminated || isRegularExpressionLiteral(contextToken)));
position === contextToken.end && (!!contextToken.isUnterminated || isRegularExpressionLiteral(contextToken)));
}
/**
@@ -2026,8 +2036,8 @@ namespace ts.Completions {
return baseSymbols.filter(propertySymbol =>
!existingMemberNames.has(propertySymbol.escapedName) &&
!!propertySymbol.declarations &&
!(getDeclarationModifierFlagsFromSymbol(propertySymbol) & ModifierFlags.Private));
!!propertySymbol.declarations &&
!(getDeclarationModifierFlagsFromSymbol(propertySymbol) & ModifierFlags.Private));
}
/**
@@ -2139,6 +2149,8 @@ namespace ts.Completions {
return isParameterPropertyModifier(kind);
case KeywordCompletionFilters.FunctionLikeBodyKeywords:
return isFunctionLikeBodyKeyword(kind);
case KeywordCompletionFilters.TypeAssertionKeywords:
return isTypeKeyword(kind) || kind === SyntaxKind.ConstKeyword;
case KeywordCompletionFilters.TypeKeywords:
return isTypeKeyword(kind);
default:
+3 -2
View File
@@ -1224,6 +1224,7 @@ namespace ts {
SyntaxKind.NullKeyword,
SyntaxKind.NumberKeyword,
SyntaxKind.ObjectKeyword,
SyntaxKind.ReadonlyKeyword,
SyntaxKind.StringKeyword,
SyntaxKind.SymbolKeyword,
SyntaxKind.TrueKeyword,
@@ -1734,8 +1735,8 @@ namespace ts {
function getSynthesizedDeepCloneWorker<T extends Node>(node: T, renameMap?: Map<Identifier>, checker?: TypeChecker, callback?: (originalNode: Node, clone: Node) => any): T {
const visited = (renameMap || checker || callback) ?
visitEachChild(node, wrapper, nullTransformationContext) :
visitEachChild(node, getSynthesizedDeepClone, nullTransformationContext);
visitEachChild(node, wrapper, nullTransformationContext) :
visitEachChild(node, getSynthesizedDeepClone, nullTransformationContext);
if (visited === node) {
// This only happens for leaf nodes - internal nodes always see their children change.
@@ -0,0 +1,25 @@
//// [controlFlowElementAccess2.ts]
declare const config: {
[key: string]: boolean | { prop: string };
};
if (typeof config['works'] !== 'boolean') {
config.works.prop = 'test'; // ok
config['works'].prop = 'test'; // error, config['works']: boolean | { 'prop': string }
}
if (typeof config.works !== 'boolean') {
config['works'].prop = 'test'; // error, config['works']: boolean | { 'prop': string }
config.works.prop = 'test'; // ok
}
//// [controlFlowElementAccess2.js]
"use strict";
if (typeof config['works'] !== 'boolean') {
config.works.prop = 'test'; // ok
config['works'].prop = 'test'; // error, config['works']: boolean | { 'prop': string }
}
if (typeof config.works !== 'boolean') {
config['works'].prop = 'test'; // error, config['works']: boolean | { 'prop': string }
config.works.prop = 'test'; // ok
}
@@ -0,0 +1,37 @@
=== tests/cases/conformance/controlFlow/controlFlowElementAccess2.ts ===
declare const config: {
>config : Symbol(config, Decl(controlFlowElementAccess2.ts, 0, 13))
[key: string]: boolean | { prop: string };
>key : Symbol(key, Decl(controlFlowElementAccess2.ts, 1, 5))
>prop : Symbol(prop, Decl(controlFlowElementAccess2.ts, 1, 30))
};
if (typeof config['works'] !== 'boolean') {
>config : Symbol(config, Decl(controlFlowElementAccess2.ts, 0, 13))
config.works.prop = 'test'; // ok
>config.works.prop : Symbol(prop, Decl(controlFlowElementAccess2.ts, 1, 30))
>config : Symbol(config, Decl(controlFlowElementAccess2.ts, 0, 13))
>prop : Symbol(prop, Decl(controlFlowElementAccess2.ts, 1, 30))
config['works'].prop = 'test'; // error, config['works']: boolean | { 'prop': string }
>config['works'].prop : Symbol(prop, Decl(controlFlowElementAccess2.ts, 1, 30))
>config : Symbol(config, Decl(controlFlowElementAccess2.ts, 0, 13))
>prop : Symbol(prop, Decl(controlFlowElementAccess2.ts, 1, 30))
}
if (typeof config.works !== 'boolean') {
>config : Symbol(config, Decl(controlFlowElementAccess2.ts, 0, 13))
config['works'].prop = 'test'; // error, config['works']: boolean | { 'prop': string }
>config['works'].prop : Symbol(prop, Decl(controlFlowElementAccess2.ts, 1, 30))
>config : Symbol(config, Decl(controlFlowElementAccess2.ts, 0, 13))
>prop : Symbol(prop, Decl(controlFlowElementAccess2.ts, 1, 30))
config.works.prop = 'test'; // ok
>config.works.prop : Symbol(prop, Decl(controlFlowElementAccess2.ts, 1, 30))
>config : Symbol(config, Decl(controlFlowElementAccess2.ts, 0, 13))
>prop : Symbol(prop, Decl(controlFlowElementAccess2.ts, 1, 30))
}
@@ -0,0 +1,63 @@
=== tests/cases/conformance/controlFlow/controlFlowElementAccess2.ts ===
declare const config: {
>config : { [key: string]: boolean | { prop: string; }; }
[key: string]: boolean | { prop: string };
>key : string
>prop : string
};
if (typeof config['works'] !== 'boolean') {
>typeof config['works'] !== 'boolean' : boolean
>typeof config['works'] : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>config['works'] : boolean | { prop: string; }
>config : { [key: string]: boolean | { prop: string; }; }
>'works' : "works"
>'boolean' : "boolean"
config.works.prop = 'test'; // ok
>config.works.prop = 'test' : "test"
>config.works.prop : string
>config.works : { prop: string; }
>config : { [key: string]: boolean | { prop: string; }; }
>works : { prop: string; }
>prop : string
>'test' : "test"
config['works'].prop = 'test'; // error, config['works']: boolean | { 'prop': string }
>config['works'].prop = 'test' : "test"
>config['works'].prop : string
>config['works'] : { prop: string; }
>config : { [key: string]: boolean | { prop: string; }; }
>'works' : "works"
>prop : string
>'test' : "test"
}
if (typeof config.works !== 'boolean') {
>typeof config.works !== 'boolean' : boolean
>typeof config.works : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>config.works : boolean | { prop: string; }
>config : { [key: string]: boolean | { prop: string; }; }
>works : boolean | { prop: string; }
>'boolean' : "boolean"
config['works'].prop = 'test'; // error, config['works']: boolean | { 'prop': string }
>config['works'].prop = 'test' : "test"
>config['works'].prop : string
>config['works'] : { prop: string; }
>config : { [key: string]: boolean | { prop: string; }; }
>'works' : "works"
>prop : string
>'test' : "test"
config.works.prop = 'test'; // ok
>config.works.prop = 'test' : "test"
>config.works.prop : string
>config.works : { prop: string; }
>config : { [key: string]: boolean | { prop: string; }; }
>works : { prop: string; }
>prop : string
>'test' : "test"
}
@@ -0,0 +1,13 @@
// @strict: true
declare const config: {
[key: string]: boolean | { prop: string };
};
if (typeof config['works'] !== 'boolean') {
config.works.prop = 'test'; // ok
config['works'].prop = 'test'; // error, config['works']: boolean | { 'prop': string }
}
if (typeof config.works !== 'boolean') {
config['works'].prop = 'test'; // error, config['works']: boolean | { 'prop': string }
config.works.prop = 'test'; // ok
}
@@ -0,0 +1,13 @@
/// <reference path='fourslash.ts'/>
////const a = {
//// b: 42 as /*0*/
////};
////
////1 as /*1*/
////
////const b = 42 as /*2*/
////
////var c = </*3*/>42
verify.completions({ marker: test.markers(), exact: completion.typeAssertionKeywords });
@@ -6,5 +6,5 @@
////function f([|readonly|] p) {}
for (const r of test.ranges()) {
verify.documentHighlightsOf(r, [r]);
verify.documentHighlightsOf(r, test.ranges());
}
+1
View File
@@ -696,6 +696,7 @@ declare namespace completion {
export const typeKeywords: ReadonlyArray<Entry>;
export const globalTypes: ReadonlyArray<Entry>;
export function globalTypesPlus(plus: ReadonlyArray<FourSlashInterface.ExpectedCompletionEntry>): ReadonlyArray<Entry>;
export const typeAssertionKeywords: ReadonlyArray<Entry>;
export const classElementKeywords: ReadonlyArray<Entry>;
export const classElementInJsKeywords: ReadonlyArray<Entry>;
export const constructorParameterKeywords: ReadonlyArray<Entry>;