diff --git a/Jakefile.js b/Jakefile.js index 501892bf840..1b5f698529b 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -154,7 +154,8 @@ var harnessSources = harnessCoreSources.concat([ "tsconfigParsing.ts", "commandLineParsing.ts", "convertCompilerOptionsFromJson.ts", - "convertTypingOptionsFromJson.ts" + "convertTypingOptionsFromJson.ts", + "tsserverProjectSystem.ts" ].map(function (f) { return path.join(unittestsDirectory, f); })).concat([ diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index fef23770475..8c48d984b78 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -77,12 +77,14 @@ namespace ts { // Blocks (when not parented by functions), Catch clauses, For/For-in/For-of statements... IsBlockScopedContainer = 1 << 1, - HasLocals = 1 << 2, + // The current node is the container of a control flow path. The current control flow should + // be saved and restored, and a new control flow initialized within the container. + IsControlFlowContainer = 1 << 2, - // If the current node is a container that also container that also contains locals. Examples: - // - // Functions, Methods, Modules, Source-files. - IsContainerWithLocals = IsContainer | HasLocals + IsFunctionLike = 1 << 3, + IsFunctionExpression = 1 << 4, + HasLocals = 1 << 5, + IsInterface = 1 << 6, } const binder = createBinder(); @@ -103,22 +105,19 @@ namespace ts { let lastContainer: Node; let seenThisKeyword: boolean; - // state used by reachability checks - let hasExplicitReturn: boolean; + // state used by control flow analysis let currentFlow: FlowNode; let currentBreakTarget: FlowLabel; let currentContinueTarget: FlowLabel; + let currentReturnTarget: FlowLabel; let currentTrueTarget: FlowLabel; let currentFalseTarget: FlowLabel; let preSwitchCaseFlow: FlowNode; let activeLabels: ActiveLabel[]; + let hasExplicitReturn: boolean; // state used for emit helpers - let hasClassExtends: boolean; - let hasAsyncFunctions: boolean; - let hasDecorators: boolean; - let hasParameterDecorators: boolean; - let hasJsxSpreadAttribute: boolean; + let emitFlags: NodeFlags; // If this file is an external module, then it is automatically in strict-mode according to // ES6. If it is not an external module, then we'll determine if it is in strict mode or @@ -156,18 +155,15 @@ namespace ts { blockScopeContainer = undefined; lastContainer = undefined; seenThisKeyword = false; - hasExplicitReturn = false; currentFlow = undefined; currentBreakTarget = undefined; currentContinueTarget = undefined; + currentReturnTarget = undefined; currentTrueTarget = undefined; currentFalseTarget = undefined; activeLabels = undefined; - hasClassExtends = false; - hasAsyncFunctions = false; - hasDecorators = false; - hasParameterDecorators = false; - hasJsxSpreadAttribute = false; + hasExplicitReturn = false; + emitFlags = NodeFlags.None; } return bindSourceFile; @@ -267,6 +263,18 @@ namespace ts { let functionType = node.parent; let index = indexOf(functionType.parameters, node); return "p" + index; + case SyntaxKind.JSDocTypedefTag: + const parentNode = node.parent && node.parent.parent; + let nameFromParentNode: string; + if (parentNode && parentNode.kind === SyntaxKind.VariableStatement) { + if ((parentNode).declarationList.declarations.length > 0) { + const nameIdentifier = (parentNode).declarationList.declarations[0].name; + if (nameIdentifier.kind === SyntaxKind.Identifier) { + nameFromParentNode = (nameIdentifier).text; + } + } + } + return nameFromParentNode; } } @@ -400,17 +408,13 @@ namespace ts { // All container nodes are kept on a linked list in declaration order. This list is used by // the getLocalNameOfContainer function in the type checker to validate that the local name // used for a container is unique. - function bindChildren(node: Node) { + function bindContainer(node: Node, containerFlags: ContainerFlags) { // Before we recurse into a node's children, we first save the existing parent, container // and block-container. Then after we pop out of processing the children, we restore // these saved values. - const saveParent = parent; const saveContainer = container; const savedBlockScopeContainer = blockScopeContainer; - // This node will now be set as the parent of all of its children as we recurse into them. - parent = node; - // Depending on what kind of node this is, we may have to adjust the current container // and block-container. If the current node is a container, then it is automatically // considered the current block-container as well. Also, for containers that we know @@ -428,115 +432,90 @@ namespace ts { // reusing a node from a previous compilation, that node may have had 'locals' created // for it. We must clear this so we don't accidentally move any stale data forward from // a previous compilation. - const containerFlags = getContainerFlags(node); if (containerFlags & ContainerFlags.IsContainer) { container = blockScopeContainer = node; - if (containerFlags & ContainerFlags.HasLocals) { container.locals = {}; } - addToContainerChain(container); } else if (containerFlags & ContainerFlags.IsBlockScopedContainer) { blockScopeContainer = node; blockScopeContainer.locals = undefined; } - - let savedHasExplicitReturn: boolean; - let savedCurrentFlow: FlowNode; - let savedBreakTarget: FlowLabel; - let savedContinueTarget: FlowLabel; - let savedActiveLabels: ActiveLabel[]; - - const kind = node.kind; - let flags = node.flags; - - // reset all reachability check related flags on node (for incremental scenarios) - flags &= ~NodeFlags.ReachabilityCheckFlags; - - // reset all emit helper flags on node (for incremental scenarios) - flags &= ~NodeFlags.EmitHelperFlags; - - if (kind === SyntaxKind.InterfaceDeclaration) { - seenThisKeyword = false; - } - - const saveState = kind === SyntaxKind.SourceFile || kind === SyntaxKind.ModuleBlock || isFunctionLikeKind(kind); - if (saveState) { - savedHasExplicitReturn = hasExplicitReturn; - savedCurrentFlow = currentFlow; - savedBreakTarget = currentBreakTarget; - savedContinueTarget = currentContinueTarget; - savedActiveLabels = activeLabels; - - hasExplicitReturn = false; - currentFlow = { flags: FlowFlags.Start }; + if (containerFlags & ContainerFlags.IsControlFlowContainer) { + const saveCurrentFlow = currentFlow; + const saveBreakTarget = currentBreakTarget; + const saveContinueTarget = currentContinueTarget; + const saveReturnTarget = currentReturnTarget; + const saveActiveLabels = activeLabels; + const saveHasExplicitReturn = hasExplicitReturn; + const isIIFE = containerFlags & ContainerFlags.IsFunctionExpression && !!getImmediatelyInvokedFunctionExpression(node); + // An IIFE is considered part of the containing control flow. Return statements behave + // similarly to break statements that exit to a label just past the statement body. + if (isIIFE) { + currentReturnTarget = createBranchLabel(); + } + else { + currentFlow = { flags: FlowFlags.Start }; + if (containerFlags & ContainerFlags.IsFunctionExpression) { + (currentFlow).container = node; + } + currentReturnTarget = undefined; + } currentBreakTarget = undefined; currentContinueTarget = undefined; activeLabels = undefined; - } - - if (isInJavaScriptFile(node) && node.jsDocComment) { - bind(node.jsDocComment); - } - - bindReachableStatement(node); - - if (!(currentFlow.flags & FlowFlags.Unreachable) && isFunctionLikeKind(kind) && nodeIsPresent((node).body)) { - flags |= NodeFlags.HasImplicitReturn; - if (hasExplicitReturn) { - flags |= NodeFlags.HasExplicitReturn; + hasExplicitReturn = false; + bindChildren(node); + // Reset all reachability check related flags on node (for incremental scenarios) + // Reset all emit helper flags on node (for incremental scenarios) + node.flags &= ~NodeFlags.ReachabilityAndEmitFlags; + if (!(currentFlow.flags & FlowFlags.Unreachable) && containerFlags & ContainerFlags.IsFunctionLike && nodeIsPresent((node).body)) { + node.flags |= NodeFlags.HasImplicitReturn; + if (hasExplicitReturn) node.flags |= NodeFlags.HasExplicitReturn; } + if (node.kind === SyntaxKind.SourceFile) { + node.flags |= emitFlags; + } + if (isIIFE) { + addAntecedent(currentReturnTarget, currentFlow); + currentFlow = finishFlowLabel(currentReturnTarget); + } + else { + currentFlow = saveCurrentFlow; + } + currentBreakTarget = saveBreakTarget; + currentContinueTarget = saveContinueTarget; + currentReturnTarget = saveReturnTarget; + activeLabels = saveActiveLabels; + hasExplicitReturn = saveHasExplicitReturn; } - - if (kind === SyntaxKind.InterfaceDeclaration) { - flags = seenThisKeyword ? flags | NodeFlags.ContainsThis : flags & ~NodeFlags.ContainsThis; + else if (containerFlags & ContainerFlags.IsInterface) { + seenThisKeyword = false; + bindChildren(node); + node.flags = seenThisKeyword ? node.flags | NodeFlags.ContainsThis : node.flags & ~NodeFlags.ContainsThis; } - - if (kind === SyntaxKind.SourceFile) { - if (hasClassExtends) { - flags |= NodeFlags.HasClassExtends; - } - if (hasDecorators) { - flags |= NodeFlags.HasDecorators; - } - if (hasParameterDecorators) { - flags |= NodeFlags.HasParamDecorators; - } - if (hasAsyncFunctions) { - flags |= NodeFlags.HasAsyncFunctions; - } - if (hasJsxSpreadAttribute) { - flags |= NodeFlags.HasJsxSpreadAttribute; - } + else { + bindChildren(node); } - - node.flags = flags; - - if (saveState) { - hasExplicitReturn = savedHasExplicitReturn; - currentFlow = savedCurrentFlow; - currentBreakTarget = savedBreakTarget; - currentContinueTarget = savedContinueTarget; - activeLabels = savedActiveLabels; - } - container = saveContainer; - parent = saveParent; blockScopeContainer = savedBlockScopeContainer; } - /** - * Returns true if node and its subnodes were successfully traversed. - * Returning false means that node was not examined and caller needs to dive into the node himself. - */ - function bindReachableStatement(node: Node): void { + function bindChildren(node: Node): void { + // Binding of JsDocComment should be done before the current block scope container changes. + // because the scope of JsDocComment should not be affected by whether the current node is a + // container or not. + if (isInJavaScriptFile(node) && node.jsDocComments) { + for (const jsDocComment of node.jsDocComments) { + bind(jsDocComment); + } + } if (checkUnreachable(node)) { forEachChild(node, bind); return; } - switch (node.kind) { case SyntaxKind.WhileStatement: bindWhileStatement(node); @@ -589,6 +568,9 @@ namespace ts { case SyntaxKind.VariableDeclaration: bindVariableDeclarationFlow(node); break; + case SyntaxKind.CallExpression: + bindCallExpressionFlow(node); + break; default: forEachChild(node, bind); break; @@ -848,6 +830,9 @@ namespace ts { bind(node.expression); if (node.kind === SyntaxKind.ReturnStatement) { hasExplicitReturn = true; + if (currentReturnTarget) { + addAntecedent(currentReturnTarget, currentFlow); + } } currentFlow = unreachableFlow; } @@ -1098,35 +1083,67 @@ namespace ts { } } + function bindCallExpressionFlow(node: CallExpression) { + // If the target of the call expression is a function expression or arrow function we have + // an immediately invoked function expression (IIFE). Initialize the flowNode property to + // the current control flow (which includes evaluation of the IIFE arguments). + let expr: Expression = node.expression; + while (expr.kind === SyntaxKind.ParenthesizedExpression) { + expr = (expr).expression; + } + if (expr.kind === SyntaxKind.FunctionExpression || expr.kind === SyntaxKind.ArrowFunction) { + forEach(node.typeArguments, bind); + forEach(node.arguments, bind); + bind(node.expression); + } + else { + forEachChild(node, bind); + } + } + function getContainerFlags(node: Node): ContainerFlags { switch (node.kind) { case SyntaxKind.ClassExpression: case SyntaxKind.ClassDeclaration: - case SyntaxKind.InterfaceDeclaration: case SyntaxKind.EnumDeclaration: case SyntaxKind.ObjectLiteralExpression: case SyntaxKind.TypeLiteral: + case SyntaxKind.JSDocTypeLiteral: case SyntaxKind.JSDocRecordType: return ContainerFlags.IsContainer; + case SyntaxKind.InterfaceDeclaration: + return ContainerFlags.IsContainer | ContainerFlags.IsInterface; + + case SyntaxKind.JSDocFunctionType: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.TypeAliasDeclaration: + return ContainerFlags.IsContainer | ContainerFlags.HasLocals; + + case SyntaxKind.SourceFile: + return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals; + + case SyntaxKind.Constructor: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: case SyntaxKind.CallSignature: case SyntaxKind.ConstructSignature: case SyntaxKind.IndexSignature: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.Constructor: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: case SyntaxKind.FunctionType: - case SyntaxKind.JSDocFunctionType: case SyntaxKind.ConstructorType: + return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals | ContainerFlags.IsFunctionLike; + case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.SourceFile: - case SyntaxKind.TypeAliasDeclaration: - return ContainerFlags.IsContainerWithLocals; + return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals | ContainerFlags.IsFunctionLike | ContainerFlags.IsFunctionExpression; + + case SyntaxKind.ModuleBlock: + return ContainerFlags.IsControlFlowContainer; + case SyntaxKind.PropertyDeclaration: + return (node).initializer ? ContainerFlags.IsControlFlowContainer : 0; case SyntaxKind.CatchClause: case SyntaxKind.ForStatement: @@ -1194,6 +1211,7 @@ namespace ts { case SyntaxKind.ObjectLiteralExpression: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.JSDocRecordType: + case SyntaxKind.JSDocTypeLiteral: // Interface/Object-types always have their children added to the 'members' of // their container. They are only accessible through an instance of their // container, and are never in scope otherwise (even inside the body of the @@ -1222,7 +1240,7 @@ namespace ts { // their container in the tree. To accomplish this, we simply add their declared // symbol to the 'locals' of the container. These symbols can then be found as // the type checker walks up the containers, checking them for matching names. - return declareSymbol(container.locals, undefined, node, symbolFlags, symbolExcludes); + return declareSymbol(container.locals, /*parent*/ undefined, node, symbolFlags, symbolExcludes); } } @@ -1560,15 +1578,9 @@ namespace ts { if (!node) { return; } - node.parent = parent; - - const savedInStrictMode = inStrictMode; - if (!savedInStrictMode) { - updateStrictMode(node); - } - - // First we bind declaration nodes to a symbol if possible. We'll both create a symbol + const saveInStrictMode = inStrictMode; + // First we bind declaration nodes to a symbol if possible. We'll both create a symbol // and then potentially add the symbol to an appropriate symbol table. Possible // destination symbol tables are: // @@ -1576,47 +1588,40 @@ namespace ts { // 2) The 'members' table of the current container's symbol. // 3) The 'locals' table of the current container. // - // However, not all symbols will end up in any of these tables. 'Anonymous' symbols + // However, not all symbols will end up in any of these tables. 'Anonymous' symbols // (like TypeLiterals for example) will not be put in any table. bindWorker(node); - - // Then we recurse into the children of the node to bind them as well. For certain - // symbols we do specialized work when we recurse. For example, we'll keep track of - // the current 'container' node when it changes. This helps us know which symbol table - // a local should go into for example. - bindChildren(node); - - inStrictMode = savedInStrictMode; - } - - function updateStrictMode(node: Node) { - switch (node.kind) { - case SyntaxKind.SourceFile: - case SyntaxKind.ModuleBlock: - updateStrictModeStatementList((node).statements); - return; - case SyntaxKind.Block: - if (isFunctionLike(node.parent)) { - updateStrictModeStatementList((node).statements); - } - return; - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - // All classes are automatically in strict mode in ES6. - inStrictMode = true; - return; + // Then we recurse into the children of the node to bind them as well. For certain + // symbols we do specialized work when we recurse. For example, we'll keep track of + // the current 'container' node when it changes. This helps us know which symbol table + // a local should go into for example. Since terminal nodes are known not to have + // children, as an optimization we don't process those. + if (node.kind > SyntaxKind.LastToken) { + const saveParent = parent; + parent = node; + const containerFlags = getContainerFlags(node); + if (containerFlags === ContainerFlags.None) { + bindChildren(node); + } + else { + bindContainer(node, containerFlags); + } + parent = saveParent; } + inStrictMode = saveInStrictMode; } function updateStrictModeStatementList(statements: NodeArray) { - for (const statement of statements) { - if (!isPrologueDirective(statement)) { - return; - } + if (!inStrictMode) { + for (const statement of statements) { + if (!isPrologueDirective(statement)) { + return; + } - if (isUseStrictPrologueDirective(statement)) { - inStrictMode = true; - return; + if (isUseStrictPrologueDirective(statement)) { + inStrictMode = true; + return; + } } } } @@ -1696,6 +1701,8 @@ namespace ts { case SyntaxKind.PropertySignature: case SyntaxKind.JSDocRecordMember: return bindPropertyOrMethodOrAccessor(node, SymbolFlags.Property | ((node).questionToken ? SymbolFlags.Optional : SymbolFlags.None), SymbolFlags.PropertyExcludes); + case SyntaxKind.JSDocPropertyTag: + return bindJSDocProperty(node); case SyntaxKind.PropertyAssignment: case SyntaxKind.ShorthandPropertyAssignment: return bindPropertyOrMethodOrAccessor(node, SymbolFlags.Property, SymbolFlags.PropertyExcludes); @@ -1703,7 +1710,7 @@ namespace ts { return bindPropertyOrMethodOrAccessor(node, SymbolFlags.EnumMember, SymbolFlags.EnumMemberExcludes); case SyntaxKind.JsxSpreadAttribute: - hasJsxSpreadAttribute = true; + emitFlags |= NodeFlags.HasJsxSpreadAttribute; return; case SyntaxKind.CallSignature: @@ -1731,6 +1738,7 @@ namespace ts { case SyntaxKind.JSDocFunctionType: return bindFunctionOrConstructorType(node); case SyntaxKind.TypeLiteral: + case SyntaxKind.JSDocTypeLiteral: case SyntaxKind.JSDocRecordType: return bindAnonymousDeclaration(node, SymbolFlags.TypeLiteral, "__type"); case SyntaxKind.ObjectLiteralExpression: @@ -1748,9 +1756,12 @@ namespace ts { // Members of classes, interfaces, and modules case SyntaxKind.ClassExpression: case SyntaxKind.ClassDeclaration: + // All classes are automatically in strict mode in ES6. + inStrictMode = true; return bindClassLikeDeclaration(node); case SyntaxKind.InterfaceDeclaration: return bindBlockScopedDeclaration(node, SymbolFlags.Interface, SymbolFlags.InterfaceExcludes); + case SyntaxKind.JSDocTypedefTag: case SyntaxKind.TypeAliasDeclaration: return bindBlockScopedDeclaration(node, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes); case SyntaxKind.EnumDeclaration: @@ -1773,7 +1784,15 @@ namespace ts { case SyntaxKind.ExportAssignment: return bindExportAssignment(node); case SyntaxKind.SourceFile: + updateStrictModeStatementList((node).statements); return bindSourceFileIfExternalModule(); + case SyntaxKind.Block: + if (!isFunctionLike(node.parent)) { + return; + } + // Fall through + case SyntaxKind.ModuleBlock: + return updateStrictModeStatementList((node).statements); } } @@ -1935,10 +1954,10 @@ namespace ts { function bindClassLikeDeclaration(node: ClassLikeDeclaration) { if (!isDeclarationFile(file) && !isInAmbientContext(node)) { if (getClassExtendsHeritageClauseElement(node) !== undefined) { - hasClassExtends = true; + emitFlags |= NodeFlags.HasClassExtends; } if (nodeIsDecorated(node)) { - hasDecorators = true; + emitFlags |= NodeFlags.HasDecorators; } } @@ -2014,8 +2033,7 @@ namespace ts { if (!isDeclarationFile(file) && !isInAmbientContext(node) && nodeIsDecorated(node)) { - hasDecorators = true; - hasParameterDecorators = true; + emitFlags |= (NodeFlags.HasDecorators | NodeFlags.HasParamDecorators); } if (inStrictMode) { @@ -2042,7 +2060,7 @@ namespace ts { function bindFunctionDeclaration(node: FunctionDeclaration) { if (!isDeclarationFile(file) && !isInAmbientContext(node)) { if (isAsyncFunctionLike(node)) { - hasAsyncFunctions = true; + emitFlags |= NodeFlags.HasAsyncFunctions; } } @@ -2059,10 +2077,12 @@ namespace ts { function bindFunctionExpression(node: FunctionExpression) { if (!isDeclarationFile(file) && !isInAmbientContext(node)) { if (isAsyncFunctionLike(node)) { - hasAsyncFunctions = true; + emitFlags |= NodeFlags.HasAsyncFunctions; } } - + if (currentFlow) { + node.flowNode = currentFlow; + } checkStrictModeFunctionName(node); const bindingName = (node).name ? (node).name.text : "__function"; return bindAnonymousDeclaration(node, SymbolFlags.Function, bindingName); @@ -2071,10 +2091,10 @@ namespace ts { function bindPropertyOrMethodOrAccessor(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags) { if (!isDeclarationFile(file) && !isInAmbientContext(node)) { if (isAsyncFunctionLike(node)) { - hasAsyncFunctions = true; + emitFlags |= NodeFlags.HasAsyncFunctions; } if (nodeIsDecorated(node)) { - hasDecorators = true; + emitFlags |= NodeFlags.HasDecorators; } } @@ -2083,6 +2103,10 @@ namespace ts { : declareSymbolAndAddToSymbolTable(node, symbolFlags, symbolExcludes); } + function bindJSDocProperty(node: JSDocPropertyTag) { + return declareSymbolAndAddToSymbolTable(node, SymbolFlags.Property, SymbolFlags.PropertyExcludes); + } + // reachability checks function shouldReportErrorOnModuleDeclaration(node: ModuleDeclaration): boolean { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c87e353e751..b8f078ea9a5 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -110,16 +110,16 @@ namespace ts { const unknownSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "unknown"); const resolvingSymbol = createSymbol(SymbolFlags.Transient, "__resolving__"); - const nullableWideningFlags = strictNullChecks ? 0 : TypeFlags.ContainsUndefinedOrNull; const anyType = createIntrinsicType(TypeFlags.Any, "any"); const stringType = createIntrinsicType(TypeFlags.String, "string"); const numberType = createIntrinsicType(TypeFlags.Number, "number"); const booleanType = createIntrinsicType(TypeFlags.Boolean, "boolean"); const esSymbolType = createIntrinsicType(TypeFlags.ESSymbol, "symbol"); const voidType = createIntrinsicType(TypeFlags.Void, "void"); - const undefinedType = createIntrinsicType(TypeFlags.Undefined | nullableWideningFlags, "undefined"); - const nullType = createIntrinsicType(TypeFlags.Null | nullableWideningFlags, "null"); - const emptyArrayElementType = createIntrinsicType(TypeFlags.Undefined | TypeFlags.ContainsUndefinedOrNull, "undefined"); + const undefinedType = createIntrinsicType(TypeFlags.Undefined, "undefined"); + const undefinedWideningType = strictNullChecks ? undefinedType : createIntrinsicType(TypeFlags.Undefined | TypeFlags.ContainsWideningType, "undefined"); + const nullType = createIntrinsicType(TypeFlags.Null, "null"); + const nullWideningType = strictNullChecks ? nullType : createIntrinsicType(TypeFlags.Null | TypeFlags.ContainsWideningType, "null"); const unknownType = createIntrinsicType(TypeFlags.Any, "unknown"); const neverType = createIntrinsicType(TypeFlags.Never, "never"); @@ -3405,7 +3405,7 @@ namespace ts { error(type.symbol.valueDeclaration, Diagnostics._0_is_referenced_directly_or_indirectly_in_its_own_base_expression, symbolToString(type.symbol)); return type.resolvedBaseConstructorType = unknownType; } - if (baseConstructorType !== unknownType && baseConstructorType !== nullType && !isConstructorType(baseConstructorType)) { + if (baseConstructorType !== unknownType && baseConstructorType !== nullWideningType && !isConstructorType(baseConstructorType)) { error(baseTypeNode.expression, Diagnostics.Type_0_is_not_a_constructor_function_type, typeToString(baseConstructorType)); return type.resolvedBaseConstructorType = unknownType; } @@ -3581,8 +3581,22 @@ namespace ts { if (!pushTypeResolution(symbol, TypeSystemPropertyName.DeclaredType)) { return unknownType; } - const declaration = getDeclarationOfKind(symbol, SyntaxKind.TypeAliasDeclaration); - let type = getTypeFromTypeNode(declaration.type); + + let type: Type; + let declaration: JSDocTypedefTag | TypeAliasDeclaration = getDeclarationOfKind(symbol, SyntaxKind.JSDocTypedefTag); + if (declaration) { + if (declaration.jsDocTypeLiteral) { + type = getTypeFromTypeNode(declaration.jsDocTypeLiteral); + } + else { + type = getTypeFromTypeNode(declaration.typeExpression.type); + } + } + else { + declaration = getDeclarationOfKind(symbol, SyntaxKind.TypeAliasDeclaration); + type = getTypeFromTypeNode(declaration.type); + } + if (popTypeResolution()) { links.typeParameters = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); if (links.typeParameters) { @@ -4997,6 +5011,7 @@ namespace ts { containsAny?: boolean; containsUndefined?: boolean; containsNull?: boolean; + containsNonWideningType?: boolean; } function addTypeToSet(typeSet: TypeSet, type: Type, typeSetKind: TypeFlags) { @@ -5007,6 +5022,7 @@ namespace ts { if (type.flags & TypeFlags.Any) typeSet.containsAny = true; if (type.flags & TypeFlags.Undefined) typeSet.containsUndefined = true; if (type.flags & TypeFlags.Null) typeSet.containsNull = true; + if (!(type.flags & TypeFlags.ContainsWideningType)) typeSet.containsNonWideningType = true; } else if (type !== neverType && !contains(typeSet, type)) { typeSet.push(type); @@ -5067,8 +5083,8 @@ namespace ts { removeSubtypes(typeSet); } if (typeSet.length === 0) { - return typeSet.containsNull ? nullType : - typeSet.containsUndefined ? undefinedType : + return typeSet.containsNull ? typeSet.containsNonWideningType ? nullType : nullWideningType : + typeSet.containsUndefined ? typeSet.containsNonWideningType ? undefinedType : undefinedWideningType : neverType; } else if (typeSet.length === 1) { @@ -5253,6 +5269,7 @@ namespace ts { case SyntaxKind.FunctionType: case SyntaxKind.ConstructorType: case SyntaxKind.TypeLiteral: + case SyntaxKind.JSDocTypeLiteral: case SyntaxKind.JSDocFunctionType: case SyntaxKind.JSDocRecordType: return getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node); @@ -5867,7 +5884,7 @@ namespace ts { if (!(target.flags & TypeFlags.Never)) { if (target.flags & TypeFlags.Any || source.flags & TypeFlags.Never) return Ternary.True; if (source.flags & TypeFlags.Undefined) { - if (!strictNullChecks || target.flags & (TypeFlags.Undefined | TypeFlags.Void) || source === emptyArrayElementType) return Ternary.True; + if (!strictNullChecks || target.flags & (TypeFlags.Undefined | TypeFlags.Void)) return Ternary.True; } if (source.flags & TypeFlags.Null) { if (!strictNullChecks || target.flags & TypeFlags.Null) return Ternary.True; @@ -6957,7 +6974,7 @@ namespace ts { if (type.flags & TypeFlags.ObjectLiteral) { for (const p of getPropertiesOfObjectType(type)) { const t = getTypeOfSymbol(p); - if (t.flags & TypeFlags.ContainsUndefinedOrNull) { + if (t.flags & TypeFlags.ContainsWideningType) { if (!reportWideningErrorsInType(t)) { error(p.valueDeclaration, Diagnostics.Object_literal_s_property_0_implicitly_has_an_1_type, p.name, typeToString(getWidenedType(t))); } @@ -7004,7 +7021,7 @@ namespace ts { } function reportErrorsFromWidening(declaration: Declaration, type: Type) { - if (produceDiagnostics && compilerOptions.noImplicitAny && type.flags & TypeFlags.ContainsUndefinedOrNull) { + if (produceDiagnostics && compilerOptions.noImplicitAny && type.flags & TypeFlags.ContainsWideningType) { // Report implicit any error within type if possible, otherwise report error on declaration if (!reportWideningErrorsInType(type)) { reportImplicitAnyError(declaration, type); @@ -7645,7 +7662,7 @@ namespace ts { getInitialTypeOfBindingElement(node); } - function getFlowTypeOfReference(reference: Node, declaredType: Type, assumeInitialized: boolean) { + function getFlowTypeOfReference(reference: Node, declaredType: Type, assumeInitialized: boolean, includeOuterFunctions: boolean) { let key: string; if (!reference.flowNode || assumeInitialized && !(declaredType.flags & TypeFlags.Narrowable)) { return declaredType; @@ -7691,15 +7708,21 @@ namespace ts { getTypeAtFlowBranchLabel(flow) : getTypeAtFlowLoopLabel(flow); } - else if (flow.flags & FlowFlags.Unreachable) { + else if (flow.flags & FlowFlags.Start) { + // Check if we should continue with the control flow of the containing function. + const container = (flow).container; + if (container && includeOuterFunctions) { + flow = container.flowNode; + continue; + } + // At the top of the flow we have the initial type. + type = initialType; + } + else { // Unreachable code errors are reported in the binding phase. Here we // simply return the declared type to reduce follow-on errors. type = declaredType; } - else { - // At the top of the flow we have the initial type. - type = initialType; - } if (flow.flags & FlowFlags.Shared) { // Record visited node and the associated type in the cache. visitedFlowNodes[visitedFlowCount] = flow; @@ -8068,6 +8091,17 @@ namespace ts { return expression; } + function isDeclarationIncludedInFlow(reference: Node, declaration: Declaration, includeOuterFunctions: boolean) { + const declarationContainer = getContainingFunctionOrModule(declaration); + let container = getContainingFunctionOrModule(reference); + while (container !== declarationContainer && + (container.kind === SyntaxKind.FunctionExpression || container.kind === SyntaxKind.ArrowFunction) && + (includeOuterFunctions || getImmediatelyInvokedFunctionExpression(container))) { + container = getContainingFunctionOrModule(container); + } + return container === declarationContainer; + } + function checkIdentifier(node: Identifier): Type { const symbol = getResolvedSymbol(node); @@ -8124,10 +8158,11 @@ namespace ts { return type; } const declaration = localOrExportSymbol.valueDeclaration; + const includeOuterFunctions = isReadonlySymbol(localOrExportSymbol); const assumeInitialized = !strictNullChecks || (type.flags & TypeFlags.Any) !== 0 || !declaration || getRootDeclaration(declaration).kind === SyntaxKind.Parameter || isInAmbientContext(declaration) || - getContainingFunctionOrModule(declaration) !== getContainingFunctionOrModule(node); - const flowType = getFlowTypeOfReference(node, type, assumeInitialized); + !isDeclarationIncludedInFlow(node, declaration, includeOuterFunctions); + const flowType = getFlowTypeOfReference(node, type, assumeInitialized, includeOuterFunctions); if (!assumeInitialized && !(getNullableKind(type) & TypeFlags.Undefined) && getNullableKind(flowType) & TypeFlags.Undefined) { error(node, Diagnostics.Variable_0_is_used_before_being_assigned, symbolToString(symbol)); // Return the declared type to reduce follow-on errors @@ -8278,7 +8313,7 @@ namespace ts { const classInstanceType = getDeclaredTypeOfSymbol(classSymbol); const baseConstructorType = getBaseConstructorTypeOfClass(classInstanceType); - return baseConstructorType === nullType; + return baseConstructorType === nullWideningType; } function checkThisExpression(node: Node): Type { @@ -8376,7 +8411,7 @@ namespace ts { if (isClassLike(container.parent)) { const symbol = getSymbolOfNode(container.parent); const type = container.flags & NodeFlags.Static ? getTypeOfSymbol(symbol) : (getDeclaredTypeOfSymbol(symbol)).thisType; - return getFlowTypeOfReference(node, type, /*assumeInitialized*/ true); + return getFlowTypeOfReference(node, type, /*assumeInitialized*/ true, /*includeOuterFunctions*/ true); } if (isInJavaScriptFile(node)) { @@ -8661,20 +8696,6 @@ namespace ts { return undefined; } - function getImmediatelyInvokedFunctionExpression(func: FunctionExpression | MethodDeclaration) { - if (isFunctionExpressionOrArrowFunction(func)) { - let prev: Node = func; - let parent: Node = func.parent; - while (parent.kind === SyntaxKind.ParenthesizedExpression) { - prev = parent; - parent = parent.parent; - } - if (parent.kind === SyntaxKind.CallExpression && (parent as CallExpression).expression === prev) { - return parent as CallExpression; - } - } - } - // In a variable, parameter or property declaration with a type annotation, // the contextual type of an initializer expression is the type of the variable, parameter or property. // Otherwise, in a parameter declaration of a contextually typed function expression, @@ -9183,7 +9204,7 @@ namespace ts { } } } - return createArrayType(elementTypes.length ? getUnionType(elementTypes) : emptyArrayElementType); + return createArrayType(elementTypes.length ? getUnionType(elementTypes) : strictNullChecks ? neverType : undefinedWideningType); } function isNumericName(name: DeclarationName): boolean { @@ -9988,7 +10009,7 @@ namespace ts { return propType; } } - return getFlowTypeOfReference(node, propType, /*assumeInitialized*/ true); + return getFlowTypeOfReference(node, propType, /*assumeInitialized*/ true, /*includeOuterFunctions*/ false); } function isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName, propertyName: string): boolean { @@ -11983,7 +12004,7 @@ namespace ts { function checkVoidExpression(node: VoidExpression): Type { checkExpression(node.expression); - return undefinedType; + return undefinedWideningType; } function checkAwaitExpression(node: AwaitExpression): Type { @@ -12390,7 +12411,7 @@ namespace ts { case SyntaxKind.InKeyword: return checkInExpression(left, right, leftType, rightType); case SyntaxKind.AmpersandAmpersandToken: - return addNullableKind(rightType, getNullableKind(leftType)); + return strictNullChecks ? addNullableKind(rightType, getNullableKind(leftType)) : rightType; case SyntaxKind.BarBarToken: return getUnionType([getNonNullableType(leftType), rightType]); case SyntaxKind.EqualsToken: @@ -12657,7 +12678,7 @@ namespace ts { case SyntaxKind.SuperKeyword: return checkSuperExpression(node); case SyntaxKind.NullKeyword: - return nullType; + return nullWideningType; case SyntaxKind.TrueKeyword: case SyntaxKind.FalseKeyword: return booleanType; @@ -12715,7 +12736,7 @@ namespace ts { case SyntaxKind.SpreadElementExpression: return checkSpreadElementExpression(node, contextualMapper); case SyntaxKind.OmittedExpression: - return undefinedType; + return undefinedWideningType; case SyntaxKind.YieldExpression: return checkYieldExpression(node); case SyntaxKind.JsxExpression: @@ -16724,7 +16745,7 @@ namespace ts { node = node.parent; } - return node.parent && node.parent.kind === SyntaxKind.TypeReference; + return node.parent && (node.parent.kind === SyntaxKind.TypeReference || node.parent.kind === SyntaxKind.JSDocTypeReference) ; } function isHeritageClauseElementIdentifier(entityName: Node): boolean { @@ -16860,7 +16881,7 @@ namespace ts { } } else if (isTypeReferenceIdentifier(entityName)) { - let meaning = entityName.parent.kind === SyntaxKind.TypeReference ? SymbolFlags.Type : SymbolFlags.Namespace; + let meaning = (entityName.parent.kind === SyntaxKind.TypeReference || entityName.parent.kind === SyntaxKind.JSDocTypeReference) ? SymbolFlags.Type : SymbolFlags.Namespace; // Include aliases in the meaning, this ensures that we do not follow aliases to where they point and instead // return the alias symbol. meaning |= SymbolFlags.Alias; @@ -17629,7 +17650,7 @@ namespace ts { // Setup global builtins addToSymbolTable(globals, builtinGlobals, Diagnostics.Declaration_name_conflicts_with_built_in_global_identifier_0); - getSymbolLinks(undefinedSymbol).type = undefinedType; + getSymbolLinks(undefinedSymbol).type = undefinedWideningType; getSymbolLinks(argumentsSymbol).type = getGlobalType("IArguments"); getSymbolLinks(unknownSymbol).type = unknownType; diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index af8dd8ceb69..034bd27b147 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -819,6 +819,10 @@ "category": "Error", "code": 1252 }, + "'{0}' tag cannot be used independently as a top level JSDoc tag.": { + "category": "Error", + "code": 1253 + }, "'with' statements are not allowed in an async function block.": { "category": "Error", "code": 1300 diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 2672efb647c..b24dd85f2f0 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -401,6 +401,15 @@ namespace ts { return visitNode(cbNode, (node).typeExpression); case SyntaxKind.JSDocTemplateTag: return visitNodes(cbNodes, (node).typeParameters); + case SyntaxKind.JSDocTypedefTag: + return visitNode(cbNode, (node).typeExpression) || + visitNode(cbNode, (node).name) || + visitNode(cbNode, (node).jsDocTypeLiteral); + case SyntaxKind.JSDocTypeLiteral: + return visitNodes(cbNodes, (node).jsDocPropertyTags); + case SyntaxKind.JSDocPropertyTag: + return visitNode(cbNode, (node).typeExpression) || + visitNode(cbNode, (node).name); } } @@ -431,7 +440,14 @@ namespace ts { /* @internal */ export function parseIsolatedJSDocComment(content: string, start?: number, length?: number) { - return Parser.JSDocParser.parseIsolatedJSDocComment(content, start, length); + const result = Parser.JSDocParser.parseIsolatedJSDocComment(content, start, length); + if (result && result.jsDocComment) { + // because the jsDocComment was parsed out of the source file, it might + // not be covered by the fixupParentReferences. + Parser.fixupParentReferences(result.jsDocComment); + } + + return result; } /* @internal */ @@ -628,9 +644,14 @@ namespace ts { if (comments) { for (const comment of comments) { const jsDocComment = JSDocParser.parseJSDocComment(node, comment.pos, comment.end - comment.pos); - if (jsDocComment) { - node.jsDocComment = jsDocComment; + if (!jsDocComment) { + continue; } + + if (!node.jsDocComments) { + node.jsDocComments = []; + } + node.jsDocComments.push(jsDocComment); } } } @@ -638,14 +659,14 @@ namespace ts { return node; } - export function fixupParentReferences(sourceFile: Node) { + export function fixupParentReferences(rootNode: Node) { // normally parent references are set during binding. However, for clients that only need // a syntax tree, and no semantic features, then the binding process is an unnecessary // overhead. This functions allows us to set all the parents, without all the expense of // binding. - let parent: Node = sourceFile; - forEachChild(sourceFile, visitNode); + let parent: Node = rootNode; + forEachChild(rootNode, visitNode); return; function visitNode(n: Node): void { @@ -658,6 +679,13 @@ namespace ts { const saveParent = parent; parent = n; forEachChild(n, visitNode); + if (n.jsDocComments) { + for (const jsDocComment of n.jsDocComments) { + jsDocComment.parent = n; + parent = jsDocComment; + forEachChild(jsDocComment, visitNode); + } + } parent = saveParent; } } @@ -2704,7 +2732,7 @@ namespace ts { // 1) async[no LineTerminator here]AsyncArrowBindingIdentifier[?Yield][no LineTerminator here]=>AsyncConciseBody[?In] // 2) CoverCallExpressionAndAsyncArrowHead[?Yield, ?Await][no LineTerminator here]=>AsyncConciseBody[?In] // Production (1) of AsyncArrowFunctionExpression is parsed in "tryParseAsyncSimpleArrowFunctionExpression". - // And production (2) is parsed in "tryParseParenthesizedArrowFunctionExpression". + // And production (2) is parsed in "tryParseParenthesizedArrowFunctionExpression". // // If we do successfully parse arrow-function, we must *not* recurse for productions 1, 2 or 3. An ArrowFunction is // not a LeftHandSideExpression, nor does it start a ConditionalExpression. So we are done @@ -3396,8 +3424,8 @@ namespace ts { if (sourceFile.languageVariant !== LanguageVariant.JSX) { return false; } - // We are in JSX context and the token is part of JSXElement. - // Fall through + // We are in JSX context and the token is part of JSXElement. + // Fall through default: return true; } @@ -4099,9 +4127,9 @@ namespace ts { const isAsync = !!(node.flags & NodeFlags.Async); node.name = isGenerator && isAsync ? doInYieldAndAwaitContext(parseOptionalIdentifier) : - isGenerator ? doInYieldContext(parseOptionalIdentifier) : - isAsync ? doInAwaitContext(parseOptionalIdentifier) : - parseOptionalIdentifier(); + isGenerator ? doInYieldContext(parseOptionalIdentifier) : + isAsync ? doInAwaitContext(parseOptionalIdentifier) : + parseOptionalIdentifier(); fillSignature(SyntaxKind.ColonToken, /*yieldContext*/ isGenerator, /*awaitContext*/ isAsync, /*requireCompleteParameterList*/ false, node); node.body = parseFunctionBlock(/*allowYield*/ isGenerator, /*allowAwait*/ isAsync, /*ignoreMissingOpenBrace*/ false); @@ -5891,7 +5919,7 @@ namespace ts { } function checkForEmptyTypeArgumentList(typeArguments: NodeArray) { - if (parseDiagnostics.length === 0 && typeArguments && typeArguments.length === 0) { + if (parseDiagnostics.length === 0 && typeArguments && typeArguments.length === 0) { const start = typeArguments.pos - "<".length; const end = skipTrivia(sourceText, typeArguments.end) + ">".length; return parseErrorAtPosition(start, end - start, Diagnostics.Type_argument_list_cannot_be_empty); @@ -6052,7 +6080,6 @@ namespace ts { Debug.assert(end <= content.length); let tags: NodeArray; - let result: JSDocComment; // Check for /** (JSDoc opening part) @@ -6159,6 +6186,8 @@ namespace ts { return handleTemplateTag(atToken, tagName); case "type": return handleTypeTag(atToken, tagName); + case "typedef": + return handleTypedefTag(atToken, tagName); } } @@ -6266,6 +6295,122 @@ namespace ts { return finishNode(result); } + function handlePropertyTag(atToken: Node, tagName: Identifier): JSDocPropertyTag { + const typeExpression = tryParseTypeExpression(); + skipWhitespace(); + const name = parseJSDocIdentifierName(); + if (!name) { + parseErrorAtPosition(scanner.getStartPos(), /*length*/ 0, Diagnostics.Identifier_expected); + return undefined; + } + + const result = createNode(SyntaxKind.JSDocPropertyTag, atToken.pos); + result.atToken = atToken; + result.tagName = tagName; + result.name = name; + result.typeExpression = typeExpression; + return finishNode(result); + } + + function handleTypedefTag(atToken: Node, tagName: Identifier): JSDocTypedefTag { + const typeExpression = tryParseTypeExpression(); + skipWhitespace(); + + const typedefTag = createNode(SyntaxKind.JSDocTypedefTag, atToken.pos); + typedefTag.atToken = atToken; + typedefTag.tagName = tagName; + typedefTag.name = parseJSDocIdentifierName(); + typedefTag.typeExpression = typeExpression; + + if (typeExpression) { + if (typeExpression.type.kind === SyntaxKind.JSDocTypeReference) { + const jsDocTypeReference = typeExpression.type; + if (jsDocTypeReference.name.kind === SyntaxKind.Identifier) { + const name = jsDocTypeReference.name; + if (name.text === "Object") { + typedefTag.jsDocTypeLiteral = scanChildTags(); + } + } + } + if (!typedefTag.jsDocTypeLiteral) { + typedefTag.jsDocTypeLiteral = typeExpression.type; + } + } + else { + typedefTag.jsDocTypeLiteral = scanChildTags(); + } + + return finishNode(typedefTag); + + function scanChildTags(): JSDocTypeLiteral { + const jsDocTypeLiteral = createNode(SyntaxKind.JSDocTypeLiteral, scanner.getStartPos()); + let resumePos = scanner.getStartPos(); + let canParseTag = true; + let seenAsterisk = false; + let parentTagTerminated = false; + + while (token !== SyntaxKind.EndOfFileToken && !parentTagTerminated) { + nextJSDocToken(); + switch (token) { + case SyntaxKind.AtToken: + if (canParseTag) { + parentTagTerminated = !tryParseChildTag(jsDocTypeLiteral); + } + seenAsterisk = false; + break; + case SyntaxKind.NewLineTrivia: + resumePos = scanner.getStartPos() - 1; + canParseTag = true; + seenAsterisk = false; + break; + case SyntaxKind.AsteriskToken: + if (seenAsterisk) { + canParseTag = false; + } + seenAsterisk = true; + break; + case SyntaxKind.Identifier: + canParseTag = false; + case SyntaxKind.EndOfFileToken: + break; + } + } + scanner.setTextPos(resumePos); + return finishNode(jsDocTypeLiteral); + } + } + + function tryParseChildTag(parentTag: JSDocTypeLiteral): boolean { + Debug.assert(token === SyntaxKind.AtToken); + const atToken = createNode(SyntaxKind.AtToken, scanner.getStartPos()); + atToken.end = scanner.getTextPos(); + nextJSDocToken(); + + const tagName = parseJSDocIdentifierName(); + if (!tagName) { + return false; + } + + switch (tagName.text) { + case "type": + if (parentTag.jsDocTypeTag) { + // already has a @type tag, terminate the parent tag now. + return false; + } + parentTag.jsDocTypeTag = handleTypeTag(atToken, tagName); + return true; + case "prop": + case "property": + if (!parentTag.jsDocPropertyTags) { + parentTag.jsDocPropertyTags = >[]; + } + const propertyTag = handlePropertyTag(atToken, tagName); + parentTag.jsDocPropertyTags.push(propertyTag); + return true; + } + return false; + } + function handleTemplateTag(atToken: Node, tagName: Identifier): JSDocTemplateTag { if (forEach(tags, t => t.kind === SyntaxKind.JSDocTemplateTag)) { parseErrorAtPosition(tagName.pos, scanner.getTokenPos() - tagName.pos, Diagnostics._0_tag_already_specified, tagName.text); @@ -6435,10 +6580,6 @@ namespace ts { node._children = undefined; } - if (node.jsDocComment) { - node.jsDocComment = undefined; - } - node.pos += delta; node.end += delta; @@ -6447,6 +6588,11 @@ namespace ts { } forEachChild(node, visitNode, visitArray); + if (node.jsDocComments) { + for (const jsDocComment of node.jsDocComments) { + forEachChild(jsDocComment, visitNode, visitArray); + } + } checkNodePositions(node, aggressiveChecks); } diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index 1a2748b38b1..82359eacc3e 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -434,7 +434,7 @@ namespace ts { } /* @internal */ - export function skipTrivia(text: string, pos: number, stopAfterLineBreak?: boolean): number { + export function skipTrivia(text: string, pos: number, stopAfterLineBreak?: boolean, stopAtComments = false): number { // Using ! with a greater than test is a fast way of testing the following conditions: // pos === undefined || pos === null || isNaN(pos) || pos < 0; if (!(pos >= 0)) { @@ -462,6 +462,9 @@ namespace ts { pos++; continue; case CharacterCodes.slash: + if (stopAtComments) { + break; + } if (text.charCodeAt(pos + 1) === CharacterCodes.slash) { pos += 2; while (pos < text.length) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 7613c489920..4c0e7a036db 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -343,6 +343,9 @@ namespace ts { JSDocReturnTag, JSDocTypeTag, JSDocTemplateTag, + JSDocTypedefTag, + JSDocPropertyTag, + JSDocTypeLiteral, // Synthesized list SyntaxList, @@ -372,6 +375,10 @@ namespace ts { FirstBinaryOperator = LessThanToken, LastBinaryOperator = CaretEqualsToken, FirstNode = QualifiedName, + FirstJSDocNode = JSDocTypeExpression, + LastJSDocNode = JSDocTypeLiteral, + FirstJSDocTagNode = JSDocComment, + LastJSDocTagNode = JSDocTypeLiteral } export const enum NodeFlags { @@ -416,6 +423,7 @@ namespace ts { ReachabilityCheckFlags = HasImplicitReturn | HasExplicitReturn, EmitHelperFlags = HasClassExtends | HasDecorators | HasParamDecorators | HasAsyncFunctions, + ReachabilityAndEmitFlags = ReachabilityCheckFlags | EmitHelperFlags, // Parsing context flags ContextFlags = DisallowInContext | YieldContext | DecoratorContext | AwaitContext | JavaScriptFile, @@ -448,7 +456,7 @@ namespace ts { modifiers?: ModifiersArray; // Array of modifiers /* @internal */ id?: number; // Unique id (used to look up NodeLinks) parent?: Node; // Parent node (initialized by binding - /* @internal */ jsDocComment?: JSDocComment; // JSDoc for the node, if it has any. Only for .js files. + /* @internal */ jsDocComments?: JSDocComment[]; // JSDoc for the node, if it has any. Only for .js files. /* @internal */ symbol?: Symbol; // Symbol declared by node (initialized by binding) /* @internal */ locals?: SymbolTable; // Locals associated with node (initialized by binding) /* @internal */ nextContainer?: Node; // Next container in declaration order (initialized by binding) @@ -612,6 +620,7 @@ namespace ts { // SyntaxKind.PropertyAssignment // SyntaxKind.ShorthandPropertyAssignment // SyntaxKind.EnumMember + // SyntaxKind.JSDocPropertyTag export interface VariableLikeDeclaration extends Declaration { propertyName?: PropertyName; dotDotDotToken?: Node; @@ -1510,6 +1519,25 @@ namespace ts { typeExpression: JSDocTypeExpression; } + // @kind(SyntaxKind.JSDocTypedefTag) + export interface JSDocTypedefTag extends JSDocTag, Declaration { + name?: Identifier; + typeExpression?: JSDocTypeExpression; + jsDocTypeLiteral?: JSDocTypeLiteral; + } + + // @kind(SyntaxKind.JSDocPropertyTag) + export interface JSDocPropertyTag extends JSDocTag, TypeElement { + name: Identifier; + typeExpression: JSDocTypeExpression; + } + + // @kind(SyntaxKind.JSDocTypeLiteral) + export interface JSDocTypeLiteral extends JSDocType { + jsDocPropertyTags?: NodeArray; + jsDocTypeTag?: JSDocTypeTag; + } + // @kind(SyntaxKind.JSDocParameterTag) export interface JSDocParameterTag extends JSDocTag { preParameterName?: Identifier; @@ -1537,6 +1565,13 @@ namespace ts { id?: number; // Node id used by flow type cache in checker } + // FlowStart represents the start of a control flow. For a function expression or arrow + // function, the container property references the function (which in turn has a flowNode + // property for the containing control flow). + export interface FlowStart extends FlowNode { + container?: FunctionExpression | ArrowFunction; + } + // FlowLabel represents a junction with multiple possible preceding control flows. export interface FlowLabel extends FlowNode { antecedents: FlowNode[]; @@ -2162,7 +2197,7 @@ namespace ts { /* @internal */ FreshObjectLiteral = 0x00100000, // Fresh object literal type /* @internal */ - ContainsUndefinedOrNull = 0x00200000, // Type is or contains undefined or null type + ContainsWideningType = 0x00200000, // Type is or contains undefined or null widening type /* @internal */ ContainsObjectLiteral = 0x00400000, // Type is or contains object literal type /* @internal */ @@ -2185,9 +2220,9 @@ namespace ts { StructuredType = ObjectType | Union | Intersection, Narrowable = Any | ObjectType | Union | TypeParameter, /* @internal */ - RequiresWidening = ContainsUndefinedOrNull | ContainsObjectLiteral, + RequiresWidening = ContainsWideningType | ContainsObjectLiteral, /* @internal */ - PropagatingFlags = ContainsUndefinedOrNull | ContainsObjectLiteral | ContainsAnyFunctionType + PropagatingFlags = ContainsWideningType | ContainsObjectLiteral | ContainsAnyFunctionType } export type DestructuringPattern = BindingPattern | ObjectLiteralExpression | ArrayLiteralExpression; @@ -2875,4 +2910,9 @@ namespace ts { /* @internal */ reattachFileDiagnostics(newFile: SourceFile): void; } + + // SyntaxKind.SyntaxList + export interface SyntaxList extends Node { + _children: Node[]; + } } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 7969389d788..74ea3459b23 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -287,16 +287,36 @@ namespace ts { return !nodeIsMissing(node); } - export function getTokenPosOfNode(node: Node, sourceFile?: SourceFile): number { + export function getTokenPosOfNode(node: Node, sourceFile?: SourceFile, includeJsDocComment?: boolean): number { // With nodes that have no width (i.e. 'Missing' nodes), we actually *don't* // want to skip trivia because this will launch us forward to the next token. if (nodeIsMissing(node)) { return node.pos; } + if (isJSDocNode(node)) { + return skipTrivia((sourceFile || getSourceFileOfNode(node)).text, node.pos, /*stopAfterLineBreak*/ false, /*stopAtComments*/ true); + } + + if (includeJsDocComment && node.jsDocComments && node.jsDocComments.length > 0) { + return getTokenPosOfNode(node.jsDocComments[0]); + } + + // For a syntax list, it is possible that one of its children has JSDocComment nodes, while + // the syntax list itself considers them as normal trivia. Therefore if we simply skip + // trivia for the list, we may have skipped the JSDocComment as well. So we should process its + // first child to determine the actual position of its first token. + if (node.kind === SyntaxKind.SyntaxList && (node)._children.length > 0) { + return getTokenPosOfNode((node)._children[0], sourceFile, includeJsDocComment); + } + return skipTrivia((sourceFile || getSourceFileOfNode(node)).text, node.pos); } + export function isJSDocNode(node: Node) { + return node.kind >= SyntaxKind.FirstJSDocNode && node.kind <= SyntaxKind.LastJSDocNode; + } + export function getNonDecoratorTokenPosOfNode(node: Node, sourceFile?: SourceFile): number { if (nodeIsMissing(node) || !node.decorators) { return getTokenPosOfNode(node, sourceFile); @@ -987,6 +1007,20 @@ namespace ts { } } + export function getImmediatelyInvokedFunctionExpression(func: Node): CallExpression { + if (func.kind === SyntaxKind.FunctionExpression || func.kind === SyntaxKind.ArrowFunction) { + let prev = func; + let parent = func.parent; + while (parent.kind === SyntaxKind.ParenthesizedExpression) { + prev = parent; + parent = parent.parent; + } + if (parent.kind === SyntaxKind.CallExpression && (parent as CallExpression).expression === prev) { + return parent as CallExpression; + } + } + } + /** * Determines whether a node is a property or element access expression for super. */ @@ -1322,21 +1356,23 @@ namespace ts { return undefined; } - const jsDocComment = getJSDocComment(node, checkParentVariableStatement); - if (!jsDocComment) { + const jsDocComments = getJSDocComments(node, checkParentVariableStatement); + if (!jsDocComments) { return undefined; } - for (const tag of jsDocComment.tags) { - if (tag.kind === kind) { - return tag; + for (const jsDocComment of jsDocComments) { + for (const tag of jsDocComment.tags) { + if (tag.kind === kind) { + return tag; + } } } } - function getJSDocComment(node: Node, checkParentVariableStatement: boolean): JSDocComment { - if (node.jsDocComment) { - return node.jsDocComment; + function getJSDocComments(node: Node, checkParentVariableStatement: boolean): JSDocComment[] { + if (node.jsDocComments) { + return node.jsDocComments; } // Try to recognize this pattern when node is initializer of variable declaration and JSDoc comments are on containing variable statement. // /** @@ -1352,7 +1388,7 @@ namespace ts { const variableStatementNode = isInitializerOfVariableDeclarationInStatement ? node.parent.parent.parent : undefined; if (variableStatementNode) { - return variableStatementNode.jsDocComment; + return variableStatementNode.jsDocComments; } // Also recognize when the node is the RHS of an assignment expression @@ -1363,12 +1399,12 @@ namespace ts { (parent as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken && parent.parent.kind === SyntaxKind.ExpressionStatement; if (isSourceOfAssignmentExpressionStatement) { - return parent.parent.jsDocComment; + return parent.parent.jsDocComments; } const isPropertyAssignmentExpression = parent && parent.kind === SyntaxKind.PropertyAssignment; if (isPropertyAssignmentExpression) { - return parent.jsDocComment; + return parent.jsDocComments; } } @@ -1393,14 +1429,16 @@ namespace ts { // annotation. const parameterName = (parameter.name).text; - const jsDocComment = getJSDocComment(parameter.parent, /*checkParentVariableStatement*/ true); - if (jsDocComment) { - for (const tag of jsDocComment.tags) { - if (tag.kind === SyntaxKind.JSDocParameterTag) { - const parameterTag = tag; - const name = parameterTag.preParameterName || parameterTag.postParameterName; - if (name.text === parameterName) { - return parameterTag; + const jsDocComments = getJSDocComments(parameter.parent, /*checkParentVariableStatement*/ true); + if (jsDocComments) { + for (const jsDocComment of jsDocComments) { + for (const tag of jsDocComment.tags) { + if (tag.kind === SyntaxKind.JSDocParameterTag) { + const parameterTag = tag; + const name = parameterTag.preParameterName || parameterTag.postParameterName; + if (name.text === parameterName) { + return parameterTag; + } } } } @@ -1525,6 +1563,7 @@ namespace ts { case SyntaxKind.TypeAliasDeclaration: case SyntaxKind.TypeParameter: case SyntaxKind.VariableDeclaration: + case SyntaxKind.JSDocTypedefTag: return true; } return false; diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 4a756ab3b1e..bd556c659c7 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -1486,6 +1486,12 @@ namespace FourSlash { this.fixCaretPosition(); } + public formatOnType(pos: number, key: string) { + const edits = this.languageService.getFormattingEditsAfterKeystroke(this.activeFile.fileName, pos, key, this.formatCodeOptions); + this.currentCaretPosition += this.applyEdits(this.activeFile.fileName, edits, /*isFormattingEdit*/ true); + this.fixCaretPosition(); + } + private updateMarkersForEdit(fileName: string, minChar: number, limChar: number, text: string) { for (let i = 0; i < this.testData.markers.length; i++) { const marker = this.testData.markers[i]; @@ -3223,6 +3229,10 @@ namespace FourSlashInterface { this.state.formatSelection(this.state.getMarkerByName(startMarker).position, this.state.getMarkerByName(endMarker).position); } + public onType(posMarker: string, key: string) { + this.state.formatOnType(this.state.getMarkerByName(posMarker).position, key); + } + public setOption(name: string, value: number): void; public setOption(name: string, value: string): void; public setOption(name: string, value: boolean): void; diff --git a/src/harness/harness.ts b/src/harness/harness.ts index d6959c2a54f..5fe5b84ac91 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -1,7 +1,7 @@ // // Copyright (c) Microsoft Corporation. All rights reserved. -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -221,6 +221,19 @@ namespace Utils { return k; } + // For some markers in SyntaxKind, we should print its original syntax name instead of + // the marker name in tests. + if (k === (ts).SyntaxKind.FirstJSDocNode || + k === (ts).SyntaxKind.LastJSDocNode || + k === (ts).SyntaxKind.FirstJSDocTagNode || + k === (ts).SyntaxKind.LastJSDocTagNode) { + for (const kindName in (ts).SyntaxKind) { + if ((ts).SyntaxKind[kindName] === k) { + return kindName; + } + } + } + return (ts).SyntaxKind[k]; } @@ -350,7 +363,7 @@ namespace Utils { assert.equal(node1.end, node2.end, "node1.end !== node2.end"); assert.equal(node1.kind, node2.kind, "node1.kind !== node2.kind"); - // call this on both nodes to ensure all propagated flags have been set (and thus can be + // call this on both nodes to ensure all propagated flags have been set (and thus can be // compared). assert.equal(ts.containsParseError(node1), ts.containsParseError(node2)); assert.equal(node1.flags, node2.flags, "node1.flags !== node2.flags"); @@ -751,7 +764,7 @@ namespace Harness { (emittedFile: string, emittedLine: number, emittedColumn: number, sourceFile: string, sourceLine: number, sourceColumn: number, sourceName: string): void; } - // Settings + // Settings export let userSpecifiedRoot = ""; export let lightMode = false; @@ -790,7 +803,7 @@ namespace Harness { fileName: string, sourceText: string, languageVersion: ts.ScriptTarget) { - // We'll only assert invariants outside of light mode. + // We'll only assert invariants outside of light mode. const shouldAssertInvariants = !Harness.lightMode; // Only set the parent nodes if we're asserting invariants. We don't need them otherwise. @@ -935,7 +948,7 @@ namespace Harness { libFiles?: string; } - // Additional options not already in ts.optionDeclarations + // Additional options not already in ts.optionDeclarations const harnessOptionDeclarations: ts.CommandLineOption[] = [ { name: "allowNonTsExtensions", type: "boolean" }, { name: "useCaseSensitiveFileNames", type: "boolean" }, @@ -1187,7 +1200,7 @@ namespace Harness { errLines.forEach(e => outputLines.push(e)); // do not count errors from lib.d.ts here, they are computed separately as numLibraryDiagnostics - // if lib.d.ts is explicitly included in input files and there are some errors in it (i.e. because of duplicate identifiers) + // if lib.d.ts is explicitly included in input files and there are some errors in it (i.e. because of duplicate identifiers) // then they will be added twice thus triggering 'total errors' assertion with condition // 'totalErrorsReportedInNonLibraryFiles + numLibraryDiagnostics + numTest262HarnessDiagnostics, diagnostics.length @@ -1497,7 +1510,7 @@ namespace Harness { }; testUnitData.push(newTestFile2); - // unit tests always list files explicitly + // unit tests always list files explicitly const parseConfigHost: ts.ParseConfigHost = { readDirectory: (name) => [] }; diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index b84654f38e1..55079c4896b 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1131,7 +1131,7 @@ namespace ts.server { else { this.log("No config files found."); } - return {}; + return configFileName ? { configFileName } : {}; } /** diff --git a/src/services/formatting/formatting.ts b/src/services/formatting/formatting.ts index 2f5f10752f4..ef8fddcfb3a 100644 --- a/src/services/formatting/formatting.ts +++ b/src/services/formatting/formatting.ts @@ -81,6 +81,12 @@ namespace ts.formatting { while (isWhiteSpace(sourceFile.text.charCodeAt(endOfFormatSpan)) && !isLineBreak(sourceFile.text.charCodeAt(endOfFormatSpan))) { endOfFormatSpan--; } + // if the character at the end of the span is a line break, we shouldn't include it, because it indicates we don't want to + // touch the current line at all. Also, on some OSes the line break consists of two characters (\r\n), we should test if the + // previous character before the end of format span is line break character as well. + if (isLineBreak(sourceFile.text.charCodeAt(endOfFormatSpan))) { + endOfFormatSpan--; + } const span = { // get start position for the previous line pos: getStartPositionOfLine(line - 1, sourceFile), diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index 57726a45cb0..92cf64eac94 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -652,6 +652,12 @@ namespace ts.NavigationBar { topItem.childItems.push(newItem); } + if (node.jsDocComments && node.jsDocComments.length > 0) { + for (const jsDocComment of node.jsDocComments) { + visitNode(jsDocComment); + } + } + // Add a level if traversing into a container if (newItem && (isFunctionLike(node) || isClassLike(node))) { const lastTop = topItem; @@ -731,6 +737,27 @@ namespace ts.NavigationBar { } const declName = declarationNameToString(decl.name); return getNavBarItem(declName, ScriptElementKind.constElement, [getNodeSpan(node)]); + case SyntaxKind.JSDocTypedefTag: + if ((node).name) { + return getNavBarItem( + (node).name.text, + ScriptElementKind.typeElement, + [getNodeSpan(node)]); + } + else { + const parentNode = node.parent && node.parent.parent; + if (parentNode && parentNode.kind === SyntaxKind.VariableStatement) { + if ((parentNode).declarationList.declarations.length > 0) { + const nameIdentifier = (parentNode).declarationList.declarations[0].name; + if (nameIdentifier.kind === SyntaxKind.Identifier) { + return getNavBarItem( + (nameIdentifier).text, + ScriptElementKind.typeElement, + [getNodeSpan(node)]); + } + } + } + } default: return undefined; } @@ -801,7 +828,7 @@ namespace ts.NavigationBar { } function getNodeSpan(node: Node) { - return node.kind === SyntaxKind.SourceFile + return node.kind === SyntaxKind.SourceFile ? createTextSpanFromBounds(node.getFullStart(), node.getEnd()) : createTextSpanFromBounds(node.getStart(), node.getEnd()); } diff --git a/src/services/services.ts b/src/services/services.ts index 35fb0887b3b..90740b257c6 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -20,7 +20,7 @@ namespace ts { getChildCount(sourceFile?: SourceFile): number; getChildAt(index: number, sourceFile?: SourceFile): Node; getChildren(sourceFile?: SourceFile): Node[]; - getStart(sourceFile?: SourceFile): number; + getStart(sourceFile?: SourceFile, includeJsDocComment?: boolean): number; getFullStart(): number; getEnd(): number; getWidth(sourceFile?: SourceFile): number; @@ -172,6 +172,9 @@ namespace ts { "static", "throws", "type", + "typedef", + "property", + "prop", "version" ]; let jsDocCompletionEntries: CompletionEntry[]; @@ -189,6 +192,7 @@ namespace ts { public end: number; public flags: NodeFlags; public parent: Node; + public jsDocComments: JSDocComment[]; private _children: Node[]; constructor(kind: SyntaxKind, pos: number, end: number) { @@ -203,8 +207,8 @@ namespace ts { return getSourceFileOfNode(this); } - public getStart(sourceFile?: SourceFile): number { - return getTokenPosOfNode(this, sourceFile); + public getStart(sourceFile?: SourceFile, includeJsDocComment?: boolean): number { + return getTokenPosOfNode(this, sourceFile, includeJsDocComment); } public getFullStart(): number { @@ -235,12 +239,14 @@ namespace ts { return (sourceFile || this.getSourceFile()).text.substring(this.getStart(), this.getEnd()); } - private addSyntheticNodes(nodes: Node[], pos: number, end: number): number { + private addSyntheticNodes(nodes: Node[], pos: number, end: number, useJSDocScanner?: boolean): number { scanner.setTextPos(pos); while (pos < end) { - const token = scanner.scan(); + const token = useJSDocScanner ? scanner.scanJSDocToken() : scanner.scan(); const textPos = scanner.getTextPos(); - nodes.push(createNode(token, pos, textPos, 0, this)); + if (textPos <= end) { + nodes.push(createNode(token, pos, textPos, 0, this)); + } pos = textPos; } return pos; @@ -270,20 +276,27 @@ namespace ts { scanner.setText((sourceFile || this.getSourceFile()).text); children = []; let pos = this.pos; + const useJSDocScanner = this.kind >= SyntaxKind.FirstJSDocTagNode && this.kind <= SyntaxKind.LastJSDocTagNode; const processNode = (node: Node) => { if (pos < node.pos) { - pos = this.addSyntheticNodes(children, pos, node.pos); + pos = this.addSyntheticNodes(children, pos, node.pos, useJSDocScanner); } children.push(node); pos = node.end; }; const processNodes = (nodes: NodeArray) => { if (pos < nodes.pos) { - pos = this.addSyntheticNodes(children, pos, nodes.pos); + pos = this.addSyntheticNodes(children, pos, nodes.pos, useJSDocScanner); } children.push(this.createSyntaxList(>nodes)); pos = nodes.end; }; + // jsDocComments need to be the first children + if (this.jsDocComments) { + for (const jsDocComment of this.jsDocComments) { + processNode(jsDocComment); + } + } forEachChild(this, processNode, processNodes); if (pos < this.end) { this.addSyntheticNodes(children, pos, this.end); @@ -5637,7 +5650,7 @@ namespace ts { const sourceFile = getValidSourceFile(fileName); - const node = getTouchingPropertyName(sourceFile, position); + const node = getTouchingPropertyName(sourceFile, position, /*includeJsDocComment*/ true); if (node === sourceFile) { return undefined; } @@ -5999,7 +6012,8 @@ namespace ts { const sourceFile = container.getSourceFile(); const tripleSlashDirectivePrefixRegex = /^\/\/\/\s*= start and (position < end or (position === end && token is keyword or identifier)) */ - export function getTouchingWord(sourceFile: SourceFile, position: number): Node { - return getTouchingToken(sourceFile, position, n => isWord(n.kind)); + export function getTouchingWord(sourceFile: SourceFile, position: number, includeJsDocComment = false): Node { + return getTouchingToken(sourceFile, position, n => isWord(n.kind), includeJsDocComment); } /* Gets the token whose text has range [start, end) and position >= start * and (position < end or (position === end && token is keyword or identifier or numeric/string literal)) */ - export function getTouchingPropertyName(sourceFile: SourceFile, position: number): Node { - return getTouchingToken(sourceFile, position, n => isPropertyName(n.kind)); + export function getTouchingPropertyName(sourceFile: SourceFile, position: number, includeJsDocComment = false): Node { + return getTouchingToken(sourceFile, position, n => isPropertyName(n.kind), includeJsDocComment); } /** Returns the token if position is in [start, end) or if position === end and includeItemAtEndPosition(token) === true */ - export function getTouchingToken(sourceFile: SourceFile, position: number, includeItemAtEndPosition?: (n: Node) => boolean): Node { - return getTokenAtPositionWorker(sourceFile, position, /*allowPositionInLeadingTrivia*/ false, includeItemAtEndPosition); + export function getTouchingToken(sourceFile: SourceFile, position: number, includeItemAtEndPosition?: (n: Node) => boolean, includeJsDocComment = false): Node { + return getTokenAtPositionWorker(sourceFile, position, /*allowPositionInLeadingTrivia*/ false, includeItemAtEndPosition, includeJsDocComment); } /** Returns a token if position is in [start-of-leading-trivia, end) */ - export function getTokenAtPosition(sourceFile: SourceFile, position: number): Node { - return getTokenAtPositionWorker(sourceFile, position, /*allowPositionInLeadingTrivia*/ true, /*includeItemAtEndPosition*/ undefined); + export function getTokenAtPosition(sourceFile: SourceFile, position: number, includeJsDocComment = false): Node { + return getTokenAtPositionWorker(sourceFile, position, /*allowPositionInLeadingTrivia*/ true, /*includeItemAtEndPosition*/ undefined, includeJsDocComment); } /** Get the token whose text contains the position */ - function getTokenAtPositionWorker(sourceFile: SourceFile, position: number, allowPositionInLeadingTrivia: boolean, includeItemAtEndPosition: (n: Node) => boolean): Node { + function getTokenAtPositionWorker(sourceFile: SourceFile, position: number, allowPositionInLeadingTrivia: boolean, includeItemAtEndPosition: (n: Node) => boolean, includeJsDocComment = false): Node { let current: Node = sourceFile; outer: while (true) { if (isToken(current)) { @@ -264,13 +264,34 @@ namespace ts { return current; } + if (includeJsDocComment) { + const jsDocChildren = ts.filter(current.getChildren(), isJSDocNode); + for (const jsDocChild of jsDocChildren) { + const start = allowPositionInLeadingTrivia ? jsDocChild.getFullStart() : jsDocChild.getStart(sourceFile, includeJsDocComment); + if (start <= position) { + const end = jsDocChild.getEnd(); + if (position < end || (position === end && jsDocChild.kind === SyntaxKind.EndOfFileToken)) { + current = jsDocChild; + continue outer; + } + else if (includeItemAtEndPosition && end === position) { + const previousToken = findPrecedingToken(position, sourceFile, jsDocChild); + if (previousToken && includeItemAtEndPosition(previousToken)) { + return previousToken; + } + } + } + } + } + // find the child that contains 'position' for (let i = 0, n = current.getChildCount(sourceFile); i < n; i++) { const child = current.getChildAt(i); - if (position < child.getFullStart() || position > child.getEnd()) { + // all jsDocComment nodes were already visited + if (isJSDocNode(child)) { continue; } - const start = allowPositionInLeadingTrivia ? child.getFullStart() : child.getStart(sourceFile); + const start = allowPositionInLeadingTrivia ? child.getFullStart() : child.getStart(sourceFile, includeJsDocComment); if (start <= position) { const end = child.getEnd(); if (position < end || (position === end && child.kind === SyntaxKind.EndOfFileToken)) { @@ -285,6 +306,7 @@ namespace ts { } } } + return current; } } @@ -423,6 +445,10 @@ namespace ts { return false; } + if (token.kind === SyntaxKind.JsxText) { + return true; + } + //
Hello |
if (token.kind === SyntaxKind.LessThanToken && token.parent.kind === SyntaxKind.JsxText) { return true; @@ -433,7 +459,7 @@ namespace ts { return true; } - //
{ + //
{ // | // } < /div> if (token && token.kind === SyntaxKind.CloseBraceToken && token.parent.kind === SyntaxKind.JsxExpression) { @@ -518,11 +544,12 @@ namespace ts { } if (node) { - const jsDocComment = node.jsDocComment; - if (jsDocComment) { - for (const tag of jsDocComment.tags) { - if (tag.pos <= position && position <= tag.end) { - return tag; + if (node.jsDocComments) { + for (const jsDocComment of node.jsDocComments) { + for (const tag of jsDocComment.tags) { + if (tag.pos <= position && position <= tag.end) { + return tag; + } } } } @@ -646,7 +673,7 @@ namespace ts { // [a, b, c] of // [x, [a, b, c] ] = someExpression - // or + // or // {x, a: {a, b, c} } = someExpression if (isArrayLiteralOrObjectLiteralDestructuringPattern(node.parent.kind === SyntaxKind.PropertyAssignment ? node.parent.parent : node.parent)) { return true; diff --git a/tests/baselines/reference/arrayLiteralWidened.js b/tests/baselines/reference/arrayLiteralWidened.js index a250c47849d..5b7346b8738 100644 --- a/tests/baselines/reference/arrayLiteralWidened.js +++ b/tests/baselines/reference/arrayLiteralWidened.js @@ -2,6 +2,7 @@ // array literals are widened upon assignment according to their element type var a = []; // any[] +var a = [,,]; var a = [null, null]; var a = [undefined, undefined]; @@ -12,11 +13,20 @@ var b = [[undefined, undefined]]; var c = [[[]]]; // any[][][] var c = [[[null]],[undefined]] + +// no widening when one or more elements are non-widening + +var x: undefined = undefined; + +var d = [x]; +var d = [, x]; +var d = [undefined, x]; //// [arrayLiteralWidened.js] // array literals are widened upon assignment according to their element type var a = []; // any[] +var a = [, ,]; var a = [null, null]; var a = [undefined, undefined]; var b = [[], [null, null]]; // any[][] @@ -24,3 +34,8 @@ var b = [[], []]; var b = [[undefined, undefined]]; var c = [[[]]]; // any[][][] var c = [[[null]], [undefined]]; +// no widening when one or more elements are non-widening +var x = undefined; +var d = [x]; +var d = [, x]; +var d = [undefined, x]; diff --git a/tests/baselines/reference/arrayLiteralWidened.symbols b/tests/baselines/reference/arrayLiteralWidened.symbols index ba058aeded1..48137812a5d 100644 --- a/tests/baselines/reference/arrayLiteralWidened.symbols +++ b/tests/baselines/reference/arrayLiteralWidened.symbols @@ -2,31 +2,53 @@ // array literals are widened upon assignment according to their element type var a = []; // any[] ->a : Symbol(a, Decl(arrayLiteralWidened.ts, 2, 3), Decl(arrayLiteralWidened.ts, 4, 3), Decl(arrayLiteralWidened.ts, 5, 3)) +>a : Symbol(a, Decl(arrayLiteralWidened.ts, 2, 3), Decl(arrayLiteralWidened.ts, 3, 3), Decl(arrayLiteralWidened.ts, 5, 3), Decl(arrayLiteralWidened.ts, 6, 3)) + +var a = [,,]; +>a : Symbol(a, Decl(arrayLiteralWidened.ts, 2, 3), Decl(arrayLiteralWidened.ts, 3, 3), Decl(arrayLiteralWidened.ts, 5, 3), Decl(arrayLiteralWidened.ts, 6, 3)) var a = [null, null]; ->a : Symbol(a, Decl(arrayLiteralWidened.ts, 2, 3), Decl(arrayLiteralWidened.ts, 4, 3), Decl(arrayLiteralWidened.ts, 5, 3)) +>a : Symbol(a, Decl(arrayLiteralWidened.ts, 2, 3), Decl(arrayLiteralWidened.ts, 3, 3), Decl(arrayLiteralWidened.ts, 5, 3), Decl(arrayLiteralWidened.ts, 6, 3)) var a = [undefined, undefined]; ->a : Symbol(a, Decl(arrayLiteralWidened.ts, 2, 3), Decl(arrayLiteralWidened.ts, 4, 3), Decl(arrayLiteralWidened.ts, 5, 3)) +>a : Symbol(a, Decl(arrayLiteralWidened.ts, 2, 3), Decl(arrayLiteralWidened.ts, 3, 3), Decl(arrayLiteralWidened.ts, 5, 3), Decl(arrayLiteralWidened.ts, 6, 3)) >undefined : Symbol(undefined) >undefined : Symbol(undefined) var b = [[], [null, null]]; // any[][] ->b : Symbol(b, Decl(arrayLiteralWidened.ts, 7, 3), Decl(arrayLiteralWidened.ts, 8, 3), Decl(arrayLiteralWidened.ts, 9, 3)) +>b : Symbol(b, Decl(arrayLiteralWidened.ts, 8, 3), Decl(arrayLiteralWidened.ts, 9, 3), Decl(arrayLiteralWidened.ts, 10, 3)) var b = [[], []]; ->b : Symbol(b, Decl(arrayLiteralWidened.ts, 7, 3), Decl(arrayLiteralWidened.ts, 8, 3), Decl(arrayLiteralWidened.ts, 9, 3)) +>b : Symbol(b, Decl(arrayLiteralWidened.ts, 8, 3), Decl(arrayLiteralWidened.ts, 9, 3), Decl(arrayLiteralWidened.ts, 10, 3)) var b = [[undefined, undefined]]; ->b : Symbol(b, Decl(arrayLiteralWidened.ts, 7, 3), Decl(arrayLiteralWidened.ts, 8, 3), Decl(arrayLiteralWidened.ts, 9, 3)) +>b : Symbol(b, Decl(arrayLiteralWidened.ts, 8, 3), Decl(arrayLiteralWidened.ts, 9, 3), Decl(arrayLiteralWidened.ts, 10, 3)) >undefined : Symbol(undefined) >undefined : Symbol(undefined) var c = [[[]]]; // any[][][] ->c : Symbol(c, Decl(arrayLiteralWidened.ts, 11, 3), Decl(arrayLiteralWidened.ts, 12, 3)) +>c : Symbol(c, Decl(arrayLiteralWidened.ts, 12, 3), Decl(arrayLiteralWidened.ts, 13, 3)) var c = [[[null]],[undefined]] ->c : Symbol(c, Decl(arrayLiteralWidened.ts, 11, 3), Decl(arrayLiteralWidened.ts, 12, 3)) +>c : Symbol(c, Decl(arrayLiteralWidened.ts, 12, 3), Decl(arrayLiteralWidened.ts, 13, 3)) >undefined : Symbol(undefined) +// no widening when one or more elements are non-widening + +var x: undefined = undefined; +>x : Symbol(x, Decl(arrayLiteralWidened.ts, 17, 3)) +>undefined : Symbol(undefined) + +var d = [x]; +>d : Symbol(d, Decl(arrayLiteralWidened.ts, 19, 3), Decl(arrayLiteralWidened.ts, 20, 3), Decl(arrayLiteralWidened.ts, 21, 3)) +>x : Symbol(x, Decl(arrayLiteralWidened.ts, 17, 3)) + +var d = [, x]; +>d : Symbol(d, Decl(arrayLiteralWidened.ts, 19, 3), Decl(arrayLiteralWidened.ts, 20, 3), Decl(arrayLiteralWidened.ts, 21, 3)) +>x : Symbol(x, Decl(arrayLiteralWidened.ts, 17, 3)) + +var d = [undefined, x]; +>d : Symbol(d, Decl(arrayLiteralWidened.ts, 19, 3), Decl(arrayLiteralWidened.ts, 20, 3), Decl(arrayLiteralWidened.ts, 21, 3)) +>undefined : Symbol(undefined) +>x : Symbol(x, Decl(arrayLiteralWidened.ts, 17, 3)) + diff --git a/tests/baselines/reference/arrayLiteralWidened.types b/tests/baselines/reference/arrayLiteralWidened.types index 83db7046eed..6237cd79a7e 100644 --- a/tests/baselines/reference/arrayLiteralWidened.types +++ b/tests/baselines/reference/arrayLiteralWidened.types @@ -5,6 +5,12 @@ var a = []; // any[] >a : any[] >[] : undefined[] +var a = [,,]; +>a : any[] +>[,,] : undefined[] +> : undefined +> : undefined + var a = [null, null]; >a : any[] >[null, null] : null[] @@ -53,3 +59,26 @@ var c = [[[null]],[undefined]] >[undefined] : undefined[] >undefined : undefined +// no widening when one or more elements are non-widening + +var x: undefined = undefined; +>x : undefined +>undefined : undefined + +var d = [x]; +>d : undefined[] +>[x] : undefined[] +>x : undefined + +var d = [, x]; +>d : undefined[] +>[, x] : undefined[] +> : undefined +>x : undefined + +var d = [undefined, x]; +>d : undefined[] +>[undefined, x] : undefined[] +>undefined : undefined +>x : undefined + diff --git a/tests/baselines/reference/constLocalsInFunctionExpressions.js b/tests/baselines/reference/constLocalsInFunctionExpressions.js new file mode 100644 index 00000000000..473de3b1a6c --- /dev/null +++ b/tests/baselines/reference/constLocalsInFunctionExpressions.js @@ -0,0 +1,73 @@ +//// [constLocalsInFunctionExpressions.ts] +declare function getStringOrNumber(): string | number; + +function f1() { + const x = getStringOrNumber(); + if (typeof x === "string") { + const f = () => x.length; + } +} + +function f2() { + const x = getStringOrNumber(); + if (typeof x !== "string") { + return; + } + const f = () => x.length; +} + +function f3() { + const x = getStringOrNumber(); + if (typeof x === "string") { + const f = function() { return x.length; }; + } +} + +function f4() { + const x = getStringOrNumber(); + if (typeof x !== "string") { + return; + } + const f = function() { return x.length; }; +} + +function f5() { + const x = getStringOrNumber(); + if (typeof x === "string") { + const f = () => () => x.length; + } +} + +//// [constLocalsInFunctionExpressions.js] +function f1() { + var x = getStringOrNumber(); + if (typeof x === "string") { + var f = function () { return x.length; }; + } +} +function f2() { + var x = getStringOrNumber(); + if (typeof x !== "string") { + return; + } + var f = function () { return x.length; }; +} +function f3() { + var x = getStringOrNumber(); + if (typeof x === "string") { + var f = function () { return x.length; }; + } +} +function f4() { + var x = getStringOrNumber(); + if (typeof x !== "string") { + return; + } + var f = function () { return x.length; }; +} +function f5() { + var x = getStringOrNumber(); + if (typeof x === "string") { + var f = function () { return function () { return x.length; }; }; + } +} diff --git a/tests/baselines/reference/constLocalsInFunctionExpressions.symbols b/tests/baselines/reference/constLocalsInFunctionExpressions.symbols new file mode 100644 index 00000000000..d9465a118aa --- /dev/null +++ b/tests/baselines/reference/constLocalsInFunctionExpressions.symbols @@ -0,0 +1,95 @@ +=== tests/cases/conformance/controlFlow/constLocalsInFunctionExpressions.ts === +declare function getStringOrNumber(): string | number; +>getStringOrNumber : Symbol(getStringOrNumber, Decl(constLocalsInFunctionExpressions.ts, 0, 0)) + +function f1() { +>f1 : Symbol(f1, Decl(constLocalsInFunctionExpressions.ts, 0, 54)) + + const x = getStringOrNumber(); +>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 3, 9)) +>getStringOrNumber : Symbol(getStringOrNumber, Decl(constLocalsInFunctionExpressions.ts, 0, 0)) + + if (typeof x === "string") { +>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 3, 9)) + + const f = () => x.length; +>f : Symbol(f, Decl(constLocalsInFunctionExpressions.ts, 5, 13)) +>x.length : Symbol(String.length, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 3, 9)) +>length : Symbol(String.length, Decl(lib.d.ts, --, --)) + } +} + +function f2() { +>f2 : Symbol(f2, Decl(constLocalsInFunctionExpressions.ts, 7, 1)) + + const x = getStringOrNumber(); +>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 10, 9)) +>getStringOrNumber : Symbol(getStringOrNumber, Decl(constLocalsInFunctionExpressions.ts, 0, 0)) + + if (typeof x !== "string") { +>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 10, 9)) + + return; + } + const f = () => x.length; +>f : Symbol(f, Decl(constLocalsInFunctionExpressions.ts, 14, 9)) +>x.length : Symbol(String.length, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 10, 9)) +>length : Symbol(String.length, Decl(lib.d.ts, --, --)) +} + +function f3() { +>f3 : Symbol(f3, Decl(constLocalsInFunctionExpressions.ts, 15, 1)) + + const x = getStringOrNumber(); +>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 18, 9)) +>getStringOrNumber : Symbol(getStringOrNumber, Decl(constLocalsInFunctionExpressions.ts, 0, 0)) + + if (typeof x === "string") { +>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 18, 9)) + + const f = function() { return x.length; }; +>f : Symbol(f, Decl(constLocalsInFunctionExpressions.ts, 20, 13)) +>x.length : Symbol(String.length, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 18, 9)) +>length : Symbol(String.length, Decl(lib.d.ts, --, --)) + } +} + +function f4() { +>f4 : Symbol(f4, Decl(constLocalsInFunctionExpressions.ts, 22, 1)) + + const x = getStringOrNumber(); +>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 25, 9)) +>getStringOrNumber : Symbol(getStringOrNumber, Decl(constLocalsInFunctionExpressions.ts, 0, 0)) + + if (typeof x !== "string") { +>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 25, 9)) + + return; + } + const f = function() { return x.length; }; +>f : Symbol(f, Decl(constLocalsInFunctionExpressions.ts, 29, 9)) +>x.length : Symbol(String.length, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 25, 9)) +>length : Symbol(String.length, Decl(lib.d.ts, --, --)) +} + +function f5() { +>f5 : Symbol(f5, Decl(constLocalsInFunctionExpressions.ts, 30, 1)) + + const x = getStringOrNumber(); +>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 33, 9)) +>getStringOrNumber : Symbol(getStringOrNumber, Decl(constLocalsInFunctionExpressions.ts, 0, 0)) + + if (typeof x === "string") { +>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 33, 9)) + + const f = () => () => x.length; +>f : Symbol(f, Decl(constLocalsInFunctionExpressions.ts, 35, 13)) +>x.length : Symbol(String.length, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 33, 9)) +>length : Symbol(String.length, Decl(lib.d.ts, --, --)) + } +} diff --git a/tests/baselines/reference/constLocalsInFunctionExpressions.types b/tests/baselines/reference/constLocalsInFunctionExpressions.types new file mode 100644 index 00000000000..e9f0086f6b1 --- /dev/null +++ b/tests/baselines/reference/constLocalsInFunctionExpressions.types @@ -0,0 +1,121 @@ +=== tests/cases/conformance/controlFlow/constLocalsInFunctionExpressions.ts === +declare function getStringOrNumber(): string | number; +>getStringOrNumber : () => string | number + +function f1() { +>f1 : () => void + + const x = getStringOrNumber(); +>x : string | number +>getStringOrNumber() : string | number +>getStringOrNumber : () => string | number + + if (typeof x === "string") { +>typeof x === "string" : boolean +>typeof x : string +>x : string | number +>"string" : string + + const f = () => x.length; +>f : () => number +>() => x.length : () => number +>x.length : number +>x : string +>length : number + } +} + +function f2() { +>f2 : () => void + + const x = getStringOrNumber(); +>x : string | number +>getStringOrNumber() : string | number +>getStringOrNumber : () => string | number + + if (typeof x !== "string") { +>typeof x !== "string" : boolean +>typeof x : string +>x : string | number +>"string" : string + + return; + } + const f = () => x.length; +>f : () => number +>() => x.length : () => number +>x.length : number +>x : string +>length : number +} + +function f3() { +>f3 : () => void + + const x = getStringOrNumber(); +>x : string | number +>getStringOrNumber() : string | number +>getStringOrNumber : () => string | number + + if (typeof x === "string") { +>typeof x === "string" : boolean +>typeof x : string +>x : string | number +>"string" : string + + const f = function() { return x.length; }; +>f : () => number +>function() { return x.length; } : () => number +>x.length : number +>x : string +>length : number + } +} + +function f4() { +>f4 : () => void + + const x = getStringOrNumber(); +>x : string | number +>getStringOrNumber() : string | number +>getStringOrNumber : () => string | number + + if (typeof x !== "string") { +>typeof x !== "string" : boolean +>typeof x : string +>x : string | number +>"string" : string + + return; + } + const f = function() { return x.length; }; +>f : () => number +>function() { return x.length; } : () => number +>x.length : number +>x : string +>length : number +} + +function f5() { +>f5 : () => void + + const x = getStringOrNumber(); +>x : string | number +>getStringOrNumber() : string | number +>getStringOrNumber : () => string | number + + if (typeof x === "string") { +>typeof x === "string" : boolean +>typeof x : string +>x : string | number +>"string" : string + + const f = () => () => x.length; +>f : () => () => number +>() => () => x.length : () => () => number +>() => x.length : () => number +>x.length : number +>x : string +>length : number + } +} diff --git a/tests/baselines/reference/controlFlowIIFE.js b/tests/baselines/reference/controlFlowIIFE.js new file mode 100644 index 00000000000..5d295b57315 --- /dev/null +++ b/tests/baselines/reference/controlFlowIIFE.js @@ -0,0 +1,89 @@ +//// [controlFlowIIFE.ts] + +declare function getStringOrNumber(): string | number; + +function f1() { + let x = getStringOrNumber(); + if (typeof x === "string") { + let n = function() { + return x.length; + }(); + } +} + +function f2() { + let x = getStringOrNumber(); + if (typeof x === "string") { + let n = (function() { + return x.length; + })(); + } +} + +function f3() { + let x = getStringOrNumber(); + let y: number; + if (typeof x === "string") { + let n = (z => x.length + y + z)(y = 1); + } +} + +// Repros from #8381 + +let maybeNumber: number | undefined; +(function () { + maybeNumber = 1; +})(); +maybeNumber++; +if (maybeNumber !== undefined) { + maybeNumber++; +} + +let test: string | undefined; +if (!test) { + throw new Error('Test is not defined'); +} +(() => { + test.slice(1); // No error +})(); + +//// [controlFlowIIFE.js] +function f1() { + var x = getStringOrNumber(); + if (typeof x === "string") { + var n = function () { + return x.length; + }(); + } +} +function f2() { + var x = getStringOrNumber(); + if (typeof x === "string") { + var n = (function () { + return x.length; + })(); + } +} +function f3() { + var x = getStringOrNumber(); + var y; + if (typeof x === "string") { + var n = (function (z) { return x.length + y + z; })(y = 1); + } +} +// Repros from #8381 +var maybeNumber; +(function () { + maybeNumber = 1; +})(); +maybeNumber++; +if (maybeNumber !== undefined) { + maybeNumber++; +} +var test; +if (!test) { + throw new Error('Test is not defined'); +} +(function () { + test.slice(1); // No error +})(); diff --git a/tests/baselines/reference/controlFlowIIFE.symbols b/tests/baselines/reference/controlFlowIIFE.symbols new file mode 100644 index 00000000000..b1b2b741158 --- /dev/null +++ b/tests/baselines/reference/controlFlowIIFE.symbols @@ -0,0 +1,111 @@ +=== tests/cases/conformance/controlFlow/controlFlowIIFE.ts === + +declare function getStringOrNumber(): string | number; +>getStringOrNumber : Symbol(getStringOrNumber, Decl(controlFlowIIFE.ts, 0, 0)) + +function f1() { +>f1 : Symbol(f1, Decl(controlFlowIIFE.ts, 1, 54)) + + let x = getStringOrNumber(); +>x : Symbol(x, Decl(controlFlowIIFE.ts, 4, 7)) +>getStringOrNumber : Symbol(getStringOrNumber, Decl(controlFlowIIFE.ts, 0, 0)) + + if (typeof x === "string") { +>x : Symbol(x, Decl(controlFlowIIFE.ts, 4, 7)) + + let n = function() { +>n : Symbol(n, Decl(controlFlowIIFE.ts, 6, 11)) + + return x.length; +>x.length : Symbol(String.length, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowIIFE.ts, 4, 7)) +>length : Symbol(String.length, Decl(lib.d.ts, --, --)) + + }(); + } +} + +function f2() { +>f2 : Symbol(f2, Decl(controlFlowIIFE.ts, 10, 1)) + + let x = getStringOrNumber(); +>x : Symbol(x, Decl(controlFlowIIFE.ts, 13, 7)) +>getStringOrNumber : Symbol(getStringOrNumber, Decl(controlFlowIIFE.ts, 0, 0)) + + if (typeof x === "string") { +>x : Symbol(x, Decl(controlFlowIIFE.ts, 13, 7)) + + let n = (function() { +>n : Symbol(n, Decl(controlFlowIIFE.ts, 15, 11)) + + return x.length; +>x.length : Symbol(String.length, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowIIFE.ts, 13, 7)) +>length : Symbol(String.length, Decl(lib.d.ts, --, --)) + + })(); + } +} + +function f3() { +>f3 : Symbol(f3, Decl(controlFlowIIFE.ts, 19, 1)) + + let x = getStringOrNumber(); +>x : Symbol(x, Decl(controlFlowIIFE.ts, 22, 7)) +>getStringOrNumber : Symbol(getStringOrNumber, Decl(controlFlowIIFE.ts, 0, 0)) + + let y: number; +>y : Symbol(y, Decl(controlFlowIIFE.ts, 23, 7)) + + if (typeof x === "string") { +>x : Symbol(x, Decl(controlFlowIIFE.ts, 22, 7)) + + let n = (z => x.length + y + z)(y = 1); +>n : Symbol(n, Decl(controlFlowIIFE.ts, 25, 11)) +>z : Symbol(z, Decl(controlFlowIIFE.ts, 25, 17)) +>x.length : Symbol(String.length, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowIIFE.ts, 22, 7)) +>length : Symbol(String.length, Decl(lib.d.ts, --, --)) +>y : Symbol(y, Decl(controlFlowIIFE.ts, 23, 7)) +>z : Symbol(z, Decl(controlFlowIIFE.ts, 25, 17)) +>y : Symbol(y, Decl(controlFlowIIFE.ts, 23, 7)) + } +} + +// Repros from #8381 + +let maybeNumber: number | undefined; +>maybeNumber : Symbol(maybeNumber, Decl(controlFlowIIFE.ts, 31, 3)) + +(function () { + maybeNumber = 1; +>maybeNumber : Symbol(maybeNumber, Decl(controlFlowIIFE.ts, 31, 3)) + +})(); +maybeNumber++; +>maybeNumber : Symbol(maybeNumber, Decl(controlFlowIIFE.ts, 31, 3)) + +if (maybeNumber !== undefined) { +>maybeNumber : Symbol(maybeNumber, Decl(controlFlowIIFE.ts, 31, 3)) +>undefined : Symbol(undefined) + + maybeNumber++; +>maybeNumber : Symbol(maybeNumber, Decl(controlFlowIIFE.ts, 31, 3)) +} + +let test: string | undefined; +>test : Symbol(test, Decl(controlFlowIIFE.ts, 40, 3)) + +if (!test) { +>test : Symbol(test, Decl(controlFlowIIFE.ts, 40, 3)) + + throw new Error('Test is not defined'); +>Error : Symbol(Error, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) +} +(() => { + test.slice(1); // No error +>test.slice : Symbol(String.slice, Decl(lib.d.ts, --, --)) +>test : Symbol(test, Decl(controlFlowIIFE.ts, 40, 3)) +>slice : Symbol(String.slice, Decl(lib.d.ts, --, --)) + +})(); diff --git a/tests/baselines/reference/controlFlowIIFE.types b/tests/baselines/reference/controlFlowIIFE.types new file mode 100644 index 00000000000..c041de04bc7 --- /dev/null +++ b/tests/baselines/reference/controlFlowIIFE.types @@ -0,0 +1,153 @@ +=== tests/cases/conformance/controlFlow/controlFlowIIFE.ts === + +declare function getStringOrNumber(): string | number; +>getStringOrNumber : () => string | number + +function f1() { +>f1 : () => void + + let x = getStringOrNumber(); +>x : string | number +>getStringOrNumber() : string | number +>getStringOrNumber : () => string | number + + if (typeof x === "string") { +>typeof x === "string" : boolean +>typeof x : string +>x : string | number +>"string" : string + + let n = function() { +>n : number +>function() { return x.length; }() : number +>function() { return x.length; } : () => number + + return x.length; +>x.length : number +>x : string +>length : number + + }(); + } +} + +function f2() { +>f2 : () => void + + let x = getStringOrNumber(); +>x : string | number +>getStringOrNumber() : string | number +>getStringOrNumber : () => string | number + + if (typeof x === "string") { +>typeof x === "string" : boolean +>typeof x : string +>x : string | number +>"string" : string + + let n = (function() { +>n : number +>(function() { return x.length; })() : number +>(function() { return x.length; }) : () => number +>function() { return x.length; } : () => number + + return x.length; +>x.length : number +>x : string +>length : number + + })(); + } +} + +function f3() { +>f3 : () => void + + let x = getStringOrNumber(); +>x : string | number +>getStringOrNumber() : string | number +>getStringOrNumber : () => string | number + + let y: number; +>y : number + + if (typeof x === "string") { +>typeof x === "string" : boolean +>typeof x : string +>x : string | number +>"string" : string + + let n = (z => x.length + y + z)(y = 1); +>n : number +>(z => x.length + y + z)(y = 1) : number +>(z => x.length + y + z) : (z: number) => number +>z => x.length + y + z : (z: number) => number +>z : number +>x.length + y + z : number +>x.length + y : number +>x.length : number +>x : string +>length : number +>y : number +>z : number +>y = 1 : number +>y : number +>1 : number + } +} + +// Repros from #8381 + +let maybeNumber: number | undefined; +>maybeNumber : number | undefined + +(function () { +>(function () { maybeNumber = 1;})() : void +>(function () { maybeNumber = 1;}) : () => void +>function () { maybeNumber = 1;} : () => void + + maybeNumber = 1; +>maybeNumber = 1 : number +>maybeNumber : number | undefined +>1 : number + +})(); +maybeNumber++; +>maybeNumber++ : number +>maybeNumber : number + +if (maybeNumber !== undefined) { +>maybeNumber !== undefined : boolean +>maybeNumber : number +>undefined : undefined + + maybeNumber++; +>maybeNumber++ : number +>maybeNumber : number +} + +let test: string | undefined; +>test : string | undefined + +if (!test) { +>!test : boolean +>test : string | undefined + + throw new Error('Test is not defined'); +>new Error('Test is not defined') : Error +>Error : ErrorConstructor +>'Test is not defined' : string +} +(() => { +>(() => { test.slice(1); // No error})() : void +>(() => { test.slice(1); // No error}) : () => void +>() => { test.slice(1); // No error} : () => void + + test.slice(1); // No error +>test.slice(1) : string +>test.slice : (start?: number | undefined, end?: number | undefined) => string +>test : string +>slice : (start?: number | undefined, end?: number | undefined) => string +>1 : number + +})(); diff --git a/tests/baselines/reference/controlFlowPropertyDeclarations.js b/tests/baselines/reference/controlFlowPropertyDeclarations.js new file mode 100644 index 00000000000..748f63ea858 --- /dev/null +++ b/tests/baselines/reference/controlFlowPropertyDeclarations.js @@ -0,0 +1,291 @@ +//// [controlFlowPropertyDeclarations.ts] +// Repro from ##8913 + +declare var require:any; + +var HTMLDOMPropertyConfig = require('react/lib/HTMLDOMPropertyConfig'); + +// Populate property map with ReactJS's attribute and property mappings +// TODO handle/use .Properties value eg: MUST_USE_PROPERTY is not HTML attr +for (var propname in HTMLDOMPropertyConfig.Properties) { + if (!HTMLDOMPropertyConfig.Properties.hasOwnProperty(propname)) { + continue; + } + + var mapFrom = HTMLDOMPropertyConfig.DOMAttributeNames[propname] || propname.toLowerCase(); +} + +/** + * Repeats a string a certain number of times. + * Also: the future is bright and consists of native string repetition: + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat + * + * @param {string} string String to repeat + * @param {number} times Number of times to repeat string. Integer. + * @see http://jsperf.com/string-repeater/2 + */ +function repeatString(string, times) { + if (times === 1) { + return string; + } + if (times < 0) { throw new Error(); } + var repeated = ''; + while (times) { + if (times & 1) { + repeated += string; + } + if (times >>= 1) { + string += string; + } + } + return repeated; +} + +/** + * Determine if the string ends with the specified substring. + * + * @param {string} haystack String to search in + * @param {string} needle String to search for + * @return {boolean} + */ +function endsWith(haystack, needle) { + return haystack.slice(-needle.length) === needle; +} + +/** + * Trim the specified substring off the string. If the string does not end + * with the specified substring, this is a no-op. + * + * @param {string} haystack String to search in + * @param {string} needle String to search for + * @return {string} + */ +function trimEnd(haystack, needle) { + return endsWith(haystack, needle) + ? haystack.slice(0, -needle.length) + : haystack; +} + +/** + * Convert a hyphenated string to camelCase. + */ +function hyphenToCamelCase(string) { + return string.replace(/-(.)/g, function(match, chr) { + return chr.toUpperCase(); + }); +} + +/** + * Determines if the specified string consists entirely of whitespace. + */ +function isEmpty(string) { + return !/[^\s]/.test(string); +} + +/** + * Determines if the CSS value can be converted from a + * 'px' suffixed string to a numeric value + * + * @param {string} value CSS property value + * @return {boolean} + */ +function isConvertiblePixelValue(value) { + return /^\d+px$/.test(value); +} + +export class HTMLtoJSX { + private output: string; + private level: number; + private _inPreTag: boolean; + + + /** + * Handles processing of the specified text node + * + * @param {TextNode} node + */ + _visitText = (node) => { + var parentTag = node.parentNode && node.parentNode.tagName.toLowerCase(); + if (parentTag === 'textarea' || parentTag === 'style') { + // Ignore text content of textareas and styles, as it will have already been moved + // to a "defaultValue" attribute and "dangerouslySetInnerHTML" attribute respectively. + return; + } + + var text = '' + + if (this._inPreTag) { + // If this text is contained within a
, we need to ensure the JSX
+      // whitespace coalescing rules don't eat the whitespace. This means
+      // wrapping newlines and sequences of two or more spaces in variables.
+      text = text
+        .replace(/\r/g, '')
+        .replace(/( {2,}|\n|\t|\{|\})/g, function(whitespace) {
+          return '{' + JSON.stringify(whitespace) + '}';
+        });
+    } else {
+      // If there's a newline in the text, adjust the indent level
+      if (text.indexOf('\n') > -1) {
+      }
+    }
+    this.output += text;
+  }
+
+
+
+};
+
+/**
+ * Handles parsing of inline styles
+ */
+export class StyleParser {
+  styles = {};
+  toJSXString = () => {
+    for (var key in this.styles) {
+      if (!this.styles.hasOwnProperty(key)) {
+      }
+    }
+  }
+}
+
+//// [controlFlowPropertyDeclarations.js]
+// Repro from ##8913
+"use strict";
+var HTMLDOMPropertyConfig = require('react/lib/HTMLDOMPropertyConfig');
+// Populate property map with ReactJS's attribute and property mappings
+// TODO handle/use .Properties value eg: MUST_USE_PROPERTY is not HTML attr
+for (var propname in HTMLDOMPropertyConfig.Properties) {
+    if (!HTMLDOMPropertyConfig.Properties.hasOwnProperty(propname)) {
+        continue;
+    }
+    var mapFrom = HTMLDOMPropertyConfig.DOMAttributeNames[propname] || propname.toLowerCase();
+}
+/**
+ * Repeats a string a certain number of times.
+ * Also: the future is bright and consists of native string repetition:
+ * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat
+ *
+ * @param {string} string  String to repeat
+ * @param {number} times   Number of times to repeat string. Integer.
+ * @see http://jsperf.com/string-repeater/2
+ */
+function repeatString(string, times) {
+    if (times === 1) {
+        return string;
+    }
+    if (times < 0) {
+        throw new Error();
+    }
+    var repeated = '';
+    while (times) {
+        if (times & 1) {
+            repeated += string;
+        }
+        if (times >>= 1) {
+            string += string;
+        }
+    }
+    return repeated;
+}
+/**
+ * Determine if the string ends with the specified substring.
+ *
+ * @param {string} haystack String to search in
+ * @param {string} needle   String to search for
+ * @return {boolean}
+ */
+function endsWith(haystack, needle) {
+    return haystack.slice(-needle.length) === needle;
+}
+/**
+ * Trim the specified substring off the string. If the string does not end
+ * with the specified substring, this is a no-op.
+ *
+ * @param {string} haystack String to search in
+ * @param {string} needle   String to search for
+ * @return {string}
+ */
+function trimEnd(haystack, needle) {
+    return endsWith(haystack, needle)
+        ? haystack.slice(0, -needle.length)
+        : haystack;
+}
+/**
+ * Convert a hyphenated string to camelCase.
+ */
+function hyphenToCamelCase(string) {
+    return string.replace(/-(.)/g, function (match, chr) {
+        return chr.toUpperCase();
+    });
+}
+/**
+ * Determines if the specified string consists entirely of whitespace.
+ */
+function isEmpty(string) {
+    return !/[^\s]/.test(string);
+}
+/**
+ * Determines if the CSS value can be converted from a
+ * 'px' suffixed string to a numeric value
+ *
+ * @param {string} value CSS property value
+ * @return {boolean}
+ */
+function isConvertiblePixelValue(value) {
+    return /^\d+px$/.test(value);
+}
+var HTMLtoJSX = (function () {
+    function HTMLtoJSX() {
+        var _this = this;
+        /**
+         * Handles processing of the specified text node
+         *
+         * @param {TextNode} node
+         */
+        this._visitText = function (node) {
+            var parentTag = node.parentNode && node.parentNode.tagName.toLowerCase();
+            if (parentTag === 'textarea' || parentTag === 'style') {
+                // Ignore text content of textareas and styles, as it will have already been moved
+                // to a "defaultValue" attribute and "dangerouslySetInnerHTML" attribute respectively.
+                return;
+            }
+            var text = '';
+            if (_this._inPreTag) {
+                // If this text is contained within a 
, we need to ensure the JSX
+                // whitespace coalescing rules don't eat the whitespace. This means
+                // wrapping newlines and sequences of two or more spaces in variables.
+                text = text
+                    .replace(/\r/g, '')
+                    .replace(/( {2,}|\n|\t|\{|\})/g, function (whitespace) {
+                    return '{' + JSON.stringify(whitespace) + '}';
+                });
+            }
+            else {
+                // If there's a newline in the text, adjust the indent level
+                if (text.indexOf('\n') > -1) {
+                }
+            }
+            _this.output += text;
+        };
+    }
+    return HTMLtoJSX;
+}());
+exports.HTMLtoJSX = HTMLtoJSX;
+;
+/**
+ * Handles parsing of inline styles
+ */
+var StyleParser = (function () {
+    function StyleParser() {
+        var _this = this;
+        this.styles = {};
+        this.toJSXString = function () {
+            for (var key in _this.styles) {
+                if (!_this.styles.hasOwnProperty(key)) {
+                }
+            }
+        };
+    }
+    return StyleParser;
+}());
+exports.StyleParser = StyleParser;
diff --git a/tests/baselines/reference/controlFlowPropertyDeclarations.symbols b/tests/baselines/reference/controlFlowPropertyDeclarations.symbols
new file mode 100644
index 00000000000..8e87d71ae33
--- /dev/null
+++ b/tests/baselines/reference/controlFlowPropertyDeclarations.symbols
@@ -0,0 +1,288 @@
+=== tests/cases/compiler/controlFlowPropertyDeclarations.ts ===
+// Repro from ##8913
+
+declare var require:any;
+>require : Symbol(require, Decl(controlFlowPropertyDeclarations.ts, 2, 11))
+
+var HTMLDOMPropertyConfig = require('react/lib/HTMLDOMPropertyConfig');
+>HTMLDOMPropertyConfig : Symbol(HTMLDOMPropertyConfig, Decl(controlFlowPropertyDeclarations.ts, 4, 3))
+>require : Symbol(require, Decl(controlFlowPropertyDeclarations.ts, 2, 11))
+
+// Populate property map with ReactJS's attribute and property mappings
+// TODO handle/use .Properties value eg: MUST_USE_PROPERTY is not HTML attr
+for (var propname in HTMLDOMPropertyConfig.Properties) {
+>propname : Symbol(propname, Decl(controlFlowPropertyDeclarations.ts, 8, 8))
+>HTMLDOMPropertyConfig : Symbol(HTMLDOMPropertyConfig, Decl(controlFlowPropertyDeclarations.ts, 4, 3))
+
+  if (!HTMLDOMPropertyConfig.Properties.hasOwnProperty(propname)) {
+>HTMLDOMPropertyConfig : Symbol(HTMLDOMPropertyConfig, Decl(controlFlowPropertyDeclarations.ts, 4, 3))
+>propname : Symbol(propname, Decl(controlFlowPropertyDeclarations.ts, 8, 8))
+
+    continue;
+  }
+
+  var mapFrom = HTMLDOMPropertyConfig.DOMAttributeNames[propname] || propname.toLowerCase();
+>mapFrom : Symbol(mapFrom, Decl(controlFlowPropertyDeclarations.ts, 13, 5))
+>HTMLDOMPropertyConfig : Symbol(HTMLDOMPropertyConfig, Decl(controlFlowPropertyDeclarations.ts, 4, 3))
+>propname : Symbol(propname, Decl(controlFlowPropertyDeclarations.ts, 8, 8))
+>propname.toLowerCase : Symbol(String.toLowerCase, Decl(lib.d.ts, --, --))
+>propname : Symbol(propname, Decl(controlFlowPropertyDeclarations.ts, 8, 8))
+>toLowerCase : Symbol(String.toLowerCase, Decl(lib.d.ts, --, --))
+}
+
+/**
+ * Repeats a string a certain number of times.
+ * Also: the future is bright and consists of native string repetition:
+ * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat
+ *
+ * @param {string} string  String to repeat
+ * @param {number} times   Number of times to repeat string. Integer.
+ * @see http://jsperf.com/string-repeater/2
+ */
+function repeatString(string, times) {
+>repeatString : Symbol(repeatString, Decl(controlFlowPropertyDeclarations.ts, 14, 1))
+>string : Symbol(string, Decl(controlFlowPropertyDeclarations.ts, 25, 22))
+>times : Symbol(times, Decl(controlFlowPropertyDeclarations.ts, 25, 29))
+
+  if (times === 1) {
+>times : Symbol(times, Decl(controlFlowPropertyDeclarations.ts, 25, 29))
+
+    return string;
+>string : Symbol(string, Decl(controlFlowPropertyDeclarations.ts, 25, 22))
+  }
+  if (times < 0) { throw new Error(); }
+>times : Symbol(times, Decl(controlFlowPropertyDeclarations.ts, 25, 29))
+>Error : Symbol(Error, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
+
+  var repeated = '';
+>repeated : Symbol(repeated, Decl(controlFlowPropertyDeclarations.ts, 30, 5))
+
+  while (times) {
+>times : Symbol(times, Decl(controlFlowPropertyDeclarations.ts, 25, 29))
+
+    if (times & 1) {
+>times : Symbol(times, Decl(controlFlowPropertyDeclarations.ts, 25, 29))
+
+      repeated += string;
+>repeated : Symbol(repeated, Decl(controlFlowPropertyDeclarations.ts, 30, 5))
+>string : Symbol(string, Decl(controlFlowPropertyDeclarations.ts, 25, 22))
+    }
+    if (times >>= 1) {
+>times : Symbol(times, Decl(controlFlowPropertyDeclarations.ts, 25, 29))
+
+      string += string;
+>string : Symbol(string, Decl(controlFlowPropertyDeclarations.ts, 25, 22))
+>string : Symbol(string, Decl(controlFlowPropertyDeclarations.ts, 25, 22))
+    }
+  }
+  return repeated;
+>repeated : Symbol(repeated, Decl(controlFlowPropertyDeclarations.ts, 30, 5))
+}
+
+/**
+ * Determine if the string ends with the specified substring.
+ *
+ * @param {string} haystack String to search in
+ * @param {string} needle   String to search for
+ * @return {boolean}
+ */
+function endsWith(haystack, needle) {
+>endsWith : Symbol(endsWith, Decl(controlFlowPropertyDeclarations.ts, 40, 1))
+>haystack : Symbol(haystack, Decl(controlFlowPropertyDeclarations.ts, 49, 18))
+>needle : Symbol(needle, Decl(controlFlowPropertyDeclarations.ts, 49, 27))
+
+  return haystack.slice(-needle.length) === needle;
+>haystack : Symbol(haystack, Decl(controlFlowPropertyDeclarations.ts, 49, 18))
+>needle : Symbol(needle, Decl(controlFlowPropertyDeclarations.ts, 49, 27))
+>needle : Symbol(needle, Decl(controlFlowPropertyDeclarations.ts, 49, 27))
+}
+
+/**
+ * Trim the specified substring off the string. If the string does not end
+ * with the specified substring, this is a no-op.
+ *
+ * @param {string} haystack String to search in
+ * @param {string} needle   String to search for
+ * @return {string}
+ */
+function trimEnd(haystack, needle) {
+>trimEnd : Symbol(trimEnd, Decl(controlFlowPropertyDeclarations.ts, 51, 1))
+>haystack : Symbol(haystack, Decl(controlFlowPropertyDeclarations.ts, 61, 17))
+>needle : Symbol(needle, Decl(controlFlowPropertyDeclarations.ts, 61, 26))
+
+  return endsWith(haystack, needle)
+>endsWith : Symbol(endsWith, Decl(controlFlowPropertyDeclarations.ts, 40, 1))
+>haystack : Symbol(haystack, Decl(controlFlowPropertyDeclarations.ts, 61, 17))
+>needle : Symbol(needle, Decl(controlFlowPropertyDeclarations.ts, 61, 26))
+
+    ? haystack.slice(0, -needle.length)
+>haystack : Symbol(haystack, Decl(controlFlowPropertyDeclarations.ts, 61, 17))
+>needle : Symbol(needle, Decl(controlFlowPropertyDeclarations.ts, 61, 26))
+
+    : haystack;
+>haystack : Symbol(haystack, Decl(controlFlowPropertyDeclarations.ts, 61, 17))
+}
+
+/**
+ * Convert a hyphenated string to camelCase.
+ */
+function hyphenToCamelCase(string) {
+>hyphenToCamelCase : Symbol(hyphenToCamelCase, Decl(controlFlowPropertyDeclarations.ts, 65, 1))
+>string : Symbol(string, Decl(controlFlowPropertyDeclarations.ts, 70, 27))
+
+  return string.replace(/-(.)/g, function(match, chr) {
+>string : Symbol(string, Decl(controlFlowPropertyDeclarations.ts, 70, 27))
+>match : Symbol(match, Decl(controlFlowPropertyDeclarations.ts, 71, 42))
+>chr : Symbol(chr, Decl(controlFlowPropertyDeclarations.ts, 71, 48))
+
+    return chr.toUpperCase();
+>chr : Symbol(chr, Decl(controlFlowPropertyDeclarations.ts, 71, 48))
+
+  });
+}
+
+/**
+ * Determines if the specified string consists entirely of whitespace.
+ */
+function isEmpty(string) {
+>isEmpty : Symbol(isEmpty, Decl(controlFlowPropertyDeclarations.ts, 74, 1))
+>string : Symbol(string, Decl(controlFlowPropertyDeclarations.ts, 79, 17))
+
+   return !/[^\s]/.test(string);
+>/[^\s]/.test : Symbol(RegExp.test, Decl(lib.d.ts, --, --))
+>test : Symbol(RegExp.test, Decl(lib.d.ts, --, --))
+>string : Symbol(string, Decl(controlFlowPropertyDeclarations.ts, 79, 17))
+}
+
+/**
+ * Determines if the CSS value can be converted from a
+ * 'px' suffixed string to a numeric value
+ *
+ * @param {string} value CSS property value
+ * @return {boolean}
+ */
+function isConvertiblePixelValue(value) {
+>isConvertiblePixelValue : Symbol(isConvertiblePixelValue, Decl(controlFlowPropertyDeclarations.ts, 81, 1))
+>value : Symbol(value, Decl(controlFlowPropertyDeclarations.ts, 90, 33))
+
+  return /^\d+px$/.test(value);
+>/^\d+px$/.test : Symbol(RegExp.test, Decl(lib.d.ts, --, --))
+>test : Symbol(RegExp.test, Decl(lib.d.ts, --, --))
+>value : Symbol(value, Decl(controlFlowPropertyDeclarations.ts, 90, 33))
+}
+
+export class HTMLtoJSX {
+>HTMLtoJSX : Symbol(HTMLtoJSX, Decl(controlFlowPropertyDeclarations.ts, 92, 1))
+
+    private output: string;
+>output : Symbol(HTMLtoJSX.output, Decl(controlFlowPropertyDeclarations.ts, 94, 24))
+
+    private level: number;
+>level : Symbol(HTMLtoJSX.level, Decl(controlFlowPropertyDeclarations.ts, 95, 27))
+
+    private _inPreTag: boolean;
+>_inPreTag : Symbol(HTMLtoJSX._inPreTag, Decl(controlFlowPropertyDeclarations.ts, 96, 26))
+
+
+  /**
+   * Handles processing of the specified text node
+   *
+   * @param {TextNode} node
+   */
+  _visitText = (node) => {
+>_visitText : Symbol(HTMLtoJSX._visitText, Decl(controlFlowPropertyDeclarations.ts, 97, 31))
+>node : Symbol(node, Decl(controlFlowPropertyDeclarations.ts, 105, 16))
+
+    var parentTag = node.parentNode && node.parentNode.tagName.toLowerCase();
+>parentTag : Symbol(parentTag, Decl(controlFlowPropertyDeclarations.ts, 106, 7))
+>node : Symbol(node, Decl(controlFlowPropertyDeclarations.ts, 105, 16))
+>node : Symbol(node, Decl(controlFlowPropertyDeclarations.ts, 105, 16))
+
+    if (parentTag === 'textarea' || parentTag === 'style') {
+>parentTag : Symbol(parentTag, Decl(controlFlowPropertyDeclarations.ts, 106, 7))
+>parentTag : Symbol(parentTag, Decl(controlFlowPropertyDeclarations.ts, 106, 7))
+
+      // Ignore text content of textareas and styles, as it will have already been moved
+      // to a "defaultValue" attribute and "dangerouslySetInnerHTML" attribute respectively.
+      return;
+    }
+
+    var text = ''
+>text : Symbol(text, Decl(controlFlowPropertyDeclarations.ts, 113, 7))
+
+    if (this._inPreTag) {
+>this._inPreTag : Symbol(HTMLtoJSX._inPreTag, Decl(controlFlowPropertyDeclarations.ts, 96, 26))
+>this : Symbol(HTMLtoJSX, Decl(controlFlowPropertyDeclarations.ts, 92, 1))
+>_inPreTag : Symbol(HTMLtoJSX._inPreTag, Decl(controlFlowPropertyDeclarations.ts, 96, 26))
+
+      // If this text is contained within a 
, we need to ensure the JSX
+      // whitespace coalescing rules don't eat the whitespace. This means
+      // wrapping newlines and sequences of two or more spaces in variables.
+      text = text
+>text : Symbol(text, Decl(controlFlowPropertyDeclarations.ts, 113, 7))
+>text        .replace(/\r/g, '')        .replace : Symbol(String.replace, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
+>text        .replace : Symbol(String.replace, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
+>text : Symbol(text, Decl(controlFlowPropertyDeclarations.ts, 113, 7))
+
+        .replace(/\r/g, '')
+>replace : Symbol(String.replace, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
+
+        .replace(/( {2,}|\n|\t|\{|\})/g, function(whitespace) {
+>replace : Symbol(String.replace, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
+>whitespace : Symbol(whitespace, Decl(controlFlowPropertyDeclarations.ts, 121, 50))
+
+          return '{' + JSON.stringify(whitespace) + '}';
+>JSON.stringify : Symbol(JSON.stringify, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
+>JSON : Symbol(JSON, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
+>stringify : Symbol(JSON.stringify, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
+>whitespace : Symbol(whitespace, Decl(controlFlowPropertyDeclarations.ts, 121, 50))
+
+        });
+    } else {
+      // If there's a newline in the text, adjust the indent level
+      if (text.indexOf('\n') > -1) {
+>text.indexOf : Symbol(String.indexOf, Decl(lib.d.ts, --, --))
+>text : Symbol(text, Decl(controlFlowPropertyDeclarations.ts, 113, 7))
+>indexOf : Symbol(String.indexOf, Decl(lib.d.ts, --, --))
+      }
+    }
+    this.output += text;
+>this.output : Symbol(HTMLtoJSX.output, Decl(controlFlowPropertyDeclarations.ts, 94, 24))
+>this : Symbol(HTMLtoJSX, Decl(controlFlowPropertyDeclarations.ts, 92, 1))
+>output : Symbol(HTMLtoJSX.output, Decl(controlFlowPropertyDeclarations.ts, 94, 24))
+>text : Symbol(text, Decl(controlFlowPropertyDeclarations.ts, 113, 7))
+  }
+
+
+
+};
+
+/**
+ * Handles parsing of inline styles
+ */
+export class StyleParser {
+>StyleParser : Symbol(StyleParser, Decl(controlFlowPropertyDeclarations.ts, 134, 2))
+
+  styles = {};
+>styles : Symbol(StyleParser.styles, Decl(controlFlowPropertyDeclarations.ts, 139, 26))
+
+  toJSXString = () => {
+>toJSXString : Symbol(StyleParser.toJSXString, Decl(controlFlowPropertyDeclarations.ts, 140, 14))
+
+    for (var key in this.styles) {
+>key : Symbol(key, Decl(controlFlowPropertyDeclarations.ts, 142, 12))
+>this.styles : Symbol(StyleParser.styles, Decl(controlFlowPropertyDeclarations.ts, 139, 26))
+>this : Symbol(StyleParser, Decl(controlFlowPropertyDeclarations.ts, 134, 2))
+>styles : Symbol(StyleParser.styles, Decl(controlFlowPropertyDeclarations.ts, 139, 26))
+
+      if (!this.styles.hasOwnProperty(key)) {
+>this.styles.hasOwnProperty : Symbol(Object.hasOwnProperty, Decl(lib.d.ts, --, --))
+>this.styles : Symbol(StyleParser.styles, Decl(controlFlowPropertyDeclarations.ts, 139, 26))
+>this : Symbol(StyleParser, Decl(controlFlowPropertyDeclarations.ts, 134, 2))
+>styles : Symbol(StyleParser.styles, Decl(controlFlowPropertyDeclarations.ts, 139, 26))
+>hasOwnProperty : Symbol(Object.hasOwnProperty, Decl(lib.d.ts, --, --))
+>key : Symbol(key, Decl(controlFlowPropertyDeclarations.ts, 142, 12))
+      }
+    }
+  }
+}
diff --git a/tests/baselines/reference/controlFlowPropertyDeclarations.types b/tests/baselines/reference/controlFlowPropertyDeclarations.types
new file mode 100644
index 00000000000..81c86e8625d
--- /dev/null
+++ b/tests/baselines/reference/controlFlowPropertyDeclarations.types
@@ -0,0 +1,383 @@
+=== tests/cases/compiler/controlFlowPropertyDeclarations.ts ===
+// Repro from ##8913
+
+declare var require:any;
+>require : any
+
+var HTMLDOMPropertyConfig = require('react/lib/HTMLDOMPropertyConfig');
+>HTMLDOMPropertyConfig : any
+>require('react/lib/HTMLDOMPropertyConfig') : any
+>require : any
+>'react/lib/HTMLDOMPropertyConfig' : string
+
+// Populate property map with ReactJS's attribute and property mappings
+// TODO handle/use .Properties value eg: MUST_USE_PROPERTY is not HTML attr
+for (var propname in HTMLDOMPropertyConfig.Properties) {
+>propname : string
+>HTMLDOMPropertyConfig.Properties : any
+>HTMLDOMPropertyConfig : any
+>Properties : any
+
+  if (!HTMLDOMPropertyConfig.Properties.hasOwnProperty(propname)) {
+>!HTMLDOMPropertyConfig.Properties.hasOwnProperty(propname) : boolean
+>HTMLDOMPropertyConfig.Properties.hasOwnProperty(propname) : any
+>HTMLDOMPropertyConfig.Properties.hasOwnProperty : any
+>HTMLDOMPropertyConfig.Properties : any
+>HTMLDOMPropertyConfig : any
+>Properties : any
+>hasOwnProperty : any
+>propname : string
+
+    continue;
+  }
+
+  var mapFrom = HTMLDOMPropertyConfig.DOMAttributeNames[propname] || propname.toLowerCase();
+>mapFrom : any
+>HTMLDOMPropertyConfig.DOMAttributeNames[propname] || propname.toLowerCase() : any
+>HTMLDOMPropertyConfig.DOMAttributeNames[propname] : any
+>HTMLDOMPropertyConfig.DOMAttributeNames : any
+>HTMLDOMPropertyConfig : any
+>DOMAttributeNames : any
+>propname : string
+>propname.toLowerCase() : string
+>propname.toLowerCase : () => string
+>propname : string
+>toLowerCase : () => string
+}
+
+/**
+ * Repeats a string a certain number of times.
+ * Also: the future is bright and consists of native string repetition:
+ * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat
+ *
+ * @param {string} string  String to repeat
+ * @param {number} times   Number of times to repeat string. Integer.
+ * @see http://jsperf.com/string-repeater/2
+ */
+function repeatString(string, times) {
+>repeatString : (string: any, times: any) => any
+>string : any
+>times : any
+
+  if (times === 1) {
+>times === 1 : boolean
+>times : any
+>1 : number
+
+    return string;
+>string : any
+  }
+  if (times < 0) { throw new Error(); }
+>times < 0 : boolean
+>times : any
+>0 : number
+>new Error() : Error
+>Error : ErrorConstructor
+
+  var repeated = '';
+>repeated : string
+>'' : string
+
+  while (times) {
+>times : any
+
+    if (times & 1) {
+>times & 1 : number
+>times : any
+>1 : number
+
+      repeated += string;
+>repeated += string : string
+>repeated : string
+>string : any
+    }
+    if (times >>= 1) {
+>times >>= 1 : number
+>times : any
+>1 : number
+
+      string += string;
+>string += string : any
+>string : any
+>string : any
+    }
+  }
+  return repeated;
+>repeated : string
+}
+
+/**
+ * Determine if the string ends with the specified substring.
+ *
+ * @param {string} haystack String to search in
+ * @param {string} needle   String to search for
+ * @return {boolean}
+ */
+function endsWith(haystack, needle) {
+>endsWith : (haystack: any, needle: any) => boolean
+>haystack : any
+>needle : any
+
+  return haystack.slice(-needle.length) === needle;
+>haystack.slice(-needle.length) === needle : boolean
+>haystack.slice(-needle.length) : any
+>haystack.slice : any
+>haystack : any
+>slice : any
+>-needle.length : number
+>needle.length : any
+>needle : any
+>length : any
+>needle : any
+}
+
+/**
+ * Trim the specified substring off the string. If the string does not end
+ * with the specified substring, this is a no-op.
+ *
+ * @param {string} haystack String to search in
+ * @param {string} needle   String to search for
+ * @return {string}
+ */
+function trimEnd(haystack, needle) {
+>trimEnd : (haystack: any, needle: any) => any
+>haystack : any
+>needle : any
+
+  return endsWith(haystack, needle)
+>endsWith(haystack, needle)    ? haystack.slice(0, -needle.length)    : haystack : any
+>endsWith(haystack, needle) : boolean
+>endsWith : (haystack: any, needle: any) => boolean
+>haystack : any
+>needle : any
+
+    ? haystack.slice(0, -needle.length)
+>haystack.slice(0, -needle.length) : any
+>haystack.slice : any
+>haystack : any
+>slice : any
+>0 : number
+>-needle.length : number
+>needle.length : any
+>needle : any
+>length : any
+
+    : haystack;
+>haystack : any
+}
+
+/**
+ * Convert a hyphenated string to camelCase.
+ */
+function hyphenToCamelCase(string) {
+>hyphenToCamelCase : (string: any) => any
+>string : any
+
+  return string.replace(/-(.)/g, function(match, chr) {
+>string.replace(/-(.)/g, function(match, chr) {    return chr.toUpperCase();  }) : any
+>string.replace : any
+>string : any
+>replace : any
+>/-(.)/g : RegExp
+>function(match, chr) {    return chr.toUpperCase();  } : (match: any, chr: any) => any
+>match : any
+>chr : any
+
+    return chr.toUpperCase();
+>chr.toUpperCase() : any
+>chr.toUpperCase : any
+>chr : any
+>toUpperCase : any
+
+  });
+}
+
+/**
+ * Determines if the specified string consists entirely of whitespace.
+ */
+function isEmpty(string) {
+>isEmpty : (string: any) => boolean
+>string : any
+
+   return !/[^\s]/.test(string);
+>!/[^\s]/.test(string) : boolean
+>/[^\s]/.test(string) : boolean
+>/[^\s]/.test : (string: string) => boolean
+>/[^\s]/ : RegExp
+>test : (string: string) => boolean
+>string : any
+}
+
+/**
+ * Determines if the CSS value can be converted from a
+ * 'px' suffixed string to a numeric value
+ *
+ * @param {string} value CSS property value
+ * @return {boolean}
+ */
+function isConvertiblePixelValue(value) {
+>isConvertiblePixelValue : (value: any) => boolean
+>value : any
+
+  return /^\d+px$/.test(value);
+>/^\d+px$/.test(value) : boolean
+>/^\d+px$/.test : (string: string) => boolean
+>/^\d+px$/ : RegExp
+>test : (string: string) => boolean
+>value : any
+}
+
+export class HTMLtoJSX {
+>HTMLtoJSX : HTMLtoJSX
+
+    private output: string;
+>output : string
+
+    private level: number;
+>level : number
+
+    private _inPreTag: boolean;
+>_inPreTag : boolean
+
+
+  /**
+   * Handles processing of the specified text node
+   *
+   * @param {TextNode} node
+   */
+  _visitText = (node) => {
+>_visitText : (node: any) => void
+>(node) => {    var parentTag = node.parentNode && node.parentNode.tagName.toLowerCase();    if (parentTag === 'textarea' || parentTag === 'style') {      // Ignore text content of textareas and styles, as it will have already been moved      // to a "defaultValue" attribute and "dangerouslySetInnerHTML" attribute respectively.      return;    }    var text = ''    if (this._inPreTag) {      // If this text is contained within a 
, we need to ensure the JSX      // whitespace coalescing rules don't eat the whitespace. This means      // wrapping newlines and sequences of two or more spaces in variables.      text = text        .replace(/\r/g, '')        .replace(/( {2,}|\n|\t|\{|\})/g, function(whitespace) {          return '{' + JSON.stringify(whitespace) + '}';        });    } else {      // If there's a newline in the text, adjust the indent level      if (text.indexOf('\n') > -1) {      }    }    this.output += text;  } : (node: any) => void
+>node : any
+
+    var parentTag = node.parentNode && node.parentNode.tagName.toLowerCase();
+>parentTag : any
+>node.parentNode && node.parentNode.tagName.toLowerCase() : any
+>node.parentNode : any
+>node : any
+>parentNode : any
+>node.parentNode.tagName.toLowerCase() : any
+>node.parentNode.tagName.toLowerCase : any
+>node.parentNode.tagName : any
+>node.parentNode : any
+>node : any
+>parentNode : any
+>tagName : any
+>toLowerCase : any
+
+    if (parentTag === 'textarea' || parentTag === 'style') {
+>parentTag === 'textarea' || parentTag === 'style' : boolean
+>parentTag === 'textarea' : boolean
+>parentTag : any
+>'textarea' : string
+>parentTag === 'style' : boolean
+>parentTag : any
+>'style' : string
+
+      // Ignore text content of textareas and styles, as it will have already been moved
+      // to a "defaultValue" attribute and "dangerouslySetInnerHTML" attribute respectively.
+      return;
+    }
+
+    var text = ''
+>text : string
+>'' : string
+
+    if (this._inPreTag) {
+>this._inPreTag : boolean
+>this : this
+>_inPreTag : boolean
+
+      // If this text is contained within a 
, we need to ensure the JSX
+      // whitespace coalescing rules don't eat the whitespace. This means
+      // wrapping newlines and sequences of two or more spaces in variables.
+      text = text
+>text = text        .replace(/\r/g, '')        .replace(/( {2,}|\n|\t|\{|\})/g, function(whitespace) {          return '{' + JSON.stringify(whitespace) + '}';        }) : string
+>text : string
+>text        .replace(/\r/g, '')        .replace(/( {2,}|\n|\t|\{|\})/g, function(whitespace) {          return '{' + JSON.stringify(whitespace) + '}';        }) : string
+>text        .replace(/\r/g, '')        .replace : { (searchValue: string, replaceValue: string): string; (searchValue: string, replacer: (substring: string, ...args: any[]) => string): string; (searchValue: RegExp, replaceValue: string): string; (searchValue: RegExp, replacer: (substring: string, ...args: any[]) => string): string; }
+>text        .replace(/\r/g, '') : string
+>text        .replace : { (searchValue: string, replaceValue: string): string; (searchValue: string, replacer: (substring: string, ...args: any[]) => string): string; (searchValue: RegExp, replaceValue: string): string; (searchValue: RegExp, replacer: (substring: string, ...args: any[]) => string): string; }
+>text : string
+
+        .replace(/\r/g, '')
+>replace : { (searchValue: string, replaceValue: string): string; (searchValue: string, replacer: (substring: string, ...args: any[]) => string): string; (searchValue: RegExp, replaceValue: string): string; (searchValue: RegExp, replacer: (substring: string, ...args: any[]) => string): string; }
+>/\r/g : RegExp
+>'' : string
+
+        .replace(/( {2,}|\n|\t|\{|\})/g, function(whitespace) {
+>replace : { (searchValue: string, replaceValue: string): string; (searchValue: string, replacer: (substring: string, ...args: any[]) => string): string; (searchValue: RegExp, replaceValue: string): string; (searchValue: RegExp, replacer: (substring: string, ...args: any[]) => string): string; }
+>/( {2,}|\n|\t|\{|\})/g : RegExp
+>function(whitespace) {          return '{' + JSON.stringify(whitespace) + '}';        } : (whitespace: string) => string
+>whitespace : string
+
+          return '{' + JSON.stringify(whitespace) + '}';
+>'{' + JSON.stringify(whitespace) + '}' : string
+>'{' + JSON.stringify(whitespace) : string
+>'{' : string
+>JSON.stringify(whitespace) : string
+>JSON.stringify : { (value: any, replacer?: (key: string, value: any) => any, space?: string | number): string; (value: any, replacer?: (number | string)[], space?: string | number): string; }
+>JSON : JSON
+>stringify : { (value: any, replacer?: (key: string, value: any) => any, space?: string | number): string; (value: any, replacer?: (number | string)[], space?: string | number): string; }
+>whitespace : string
+>'}' : string
+
+        });
+    } else {
+      // If there's a newline in the text, adjust the indent level
+      if (text.indexOf('\n') > -1) {
+>text.indexOf('\n') > -1 : boolean
+>text.indexOf('\n') : number
+>text.indexOf : (searchString: string, position?: number) => number
+>text : string
+>indexOf : (searchString: string, position?: number) => number
+>'\n' : string
+>-1 : number
+>1 : number
+      }
+    }
+    this.output += text;
+>this.output += text : string
+>this.output : string
+>this : this
+>output : string
+>text : string
+  }
+
+
+
+};
+
+/**
+ * Handles parsing of inline styles
+ */
+export class StyleParser {
+>StyleParser : StyleParser
+
+  styles = {};
+>styles : {}
+>{} : {}
+
+  toJSXString = () => {
+>toJSXString : () => void
+>() => {    for (var key in this.styles) {      if (!this.styles.hasOwnProperty(key)) {      }    }  } : () => void
+
+    for (var key in this.styles) {
+>key : string
+>this.styles : {}
+>this : this
+>styles : {}
+
+      if (!this.styles.hasOwnProperty(key)) {
+>!this.styles.hasOwnProperty(key) : boolean
+>this.styles.hasOwnProperty(key) : boolean
+>this.styles.hasOwnProperty : (v: string) => boolean
+>this.styles : {}
+>this : this
+>styles : {}
+>hasOwnProperty : (v: string) => boolean
+>key : string
+      }
+    }
+  }
+}
diff --git a/tests/baselines/reference/initializersWidened.js b/tests/baselines/reference/initializersWidened.js
index fce0750a600..3954735ee6e 100644
--- a/tests/baselines/reference/initializersWidened.js
+++ b/tests/baselines/reference/initializersWidened.js
@@ -1,10 +1,44 @@
 //// [initializersWidened.ts]
 // these are widened to any at the point of assignment
 
-var x = null;
-var y = undefined;
+var x1 = null;
+var y1 = undefined;
+var z1 = void 0;
+
+// these are not widened
+
+var x2: null;
+var y2: undefined;
+
+var x3: null = null;
+var y3: undefined = undefined;
+var z3: undefined = void 0;
+
+// widen only when all constituents of union are widening
+
+var x4 = null || null;
+var y4 = undefined || undefined;
+var z4 = void 0 || void 0;
+
+var x5 = null || x2;
+var y5 = undefined || y2;
+var z5 = void 0 || y2;
 
 //// [initializersWidened.js]
 // these are widened to any at the point of assignment
-var x = null;
-var y = undefined;
+var x1 = null;
+var y1 = undefined;
+var z1 = void 0;
+// these are not widened
+var x2;
+var y2;
+var x3 = null;
+var y3 = undefined;
+var z3 = void 0;
+// widen only when all constituents of union are widening
+var x4 = null || null;
+var y4 = undefined || undefined;
+var z4 = void 0 || void 0;
+var x5 = null || x2;
+var y5 = undefined || y2;
+var z5 = void 0 || y2;
diff --git a/tests/baselines/reference/initializersWidened.symbols b/tests/baselines/reference/initializersWidened.symbols
index 625c066a058..252a248bec8 100644
--- a/tests/baselines/reference/initializersWidened.symbols
+++ b/tests/baselines/reference/initializersWidened.symbols
@@ -1,10 +1,57 @@
 === tests/cases/conformance/types/typeRelationships/widenedTypes/initializersWidened.ts ===
 // these are widened to any at the point of assignment
 
-var x = null;
->x : Symbol(x, Decl(initializersWidened.ts, 2, 3))
+var x1 = null;
+>x1 : Symbol(x1, Decl(initializersWidened.ts, 2, 3))
 
-var y = undefined;
->y : Symbol(y, Decl(initializersWidened.ts, 3, 3))
+var y1 = undefined;
+>y1 : Symbol(y1, Decl(initializersWidened.ts, 3, 3))
 >undefined : Symbol(undefined)
 
+var z1 = void 0;
+>z1 : Symbol(z1, Decl(initializersWidened.ts, 4, 3))
+
+// these are not widened
+
+var x2: null;
+>x2 : Symbol(x2, Decl(initializersWidened.ts, 8, 3))
+
+var y2: undefined;
+>y2 : Symbol(y2, Decl(initializersWidened.ts, 9, 3))
+
+var x3: null = null;
+>x3 : Symbol(x3, Decl(initializersWidened.ts, 11, 3))
+
+var y3: undefined = undefined;
+>y3 : Symbol(y3, Decl(initializersWidened.ts, 12, 3))
+>undefined : Symbol(undefined)
+
+var z3: undefined = void 0;
+>z3 : Symbol(z3, Decl(initializersWidened.ts, 13, 3))
+
+// widen only when all constituents of union are widening
+
+var x4 = null || null;
+>x4 : Symbol(x4, Decl(initializersWidened.ts, 17, 3))
+
+var y4 = undefined || undefined;
+>y4 : Symbol(y4, Decl(initializersWidened.ts, 18, 3))
+>undefined : Symbol(undefined)
+>undefined : Symbol(undefined)
+
+var z4 = void 0 || void 0;
+>z4 : Symbol(z4, Decl(initializersWidened.ts, 19, 3))
+
+var x5 = null || x2;
+>x5 : Symbol(x5, Decl(initializersWidened.ts, 21, 3))
+>x2 : Symbol(x2, Decl(initializersWidened.ts, 8, 3))
+
+var y5 = undefined || y2;
+>y5 : Symbol(y5, Decl(initializersWidened.ts, 22, 3))
+>undefined : Symbol(undefined)
+>y2 : Symbol(y2, Decl(initializersWidened.ts, 9, 3))
+
+var z5 = void 0 || y2;
+>z5 : Symbol(z5, Decl(initializersWidened.ts, 23, 3))
+>y2 : Symbol(y2, Decl(initializersWidened.ts, 9, 3))
+
diff --git a/tests/baselines/reference/initializersWidened.types b/tests/baselines/reference/initializersWidened.types
index 15705589246..766f859029a 100644
--- a/tests/baselines/reference/initializersWidened.types
+++ b/tests/baselines/reference/initializersWidened.types
@@ -1,11 +1,80 @@
 === tests/cases/conformance/types/typeRelationships/widenedTypes/initializersWidened.ts ===
 // these are widened to any at the point of assignment
 
-var x = null;
->x : any
+var x1 = null;
+>x1 : any
 >null : null
 
-var y = undefined;
->y : any
+var y1 = undefined;
+>y1 : any
 >undefined : undefined
 
+var z1 = void 0;
+>z1 : any
+>void 0 : undefined
+>0 : number
+
+// these are not widened
+
+var x2: null;
+>x2 : null
+>null : null
+
+var y2: undefined;
+>y2 : undefined
+
+var x3: null = null;
+>x3 : null
+>null : null
+>null : null
+
+var y3: undefined = undefined;
+>y3 : undefined
+>undefined : undefined
+
+var z3: undefined = void 0;
+>z3 : undefined
+>void 0 : undefined
+>0 : number
+
+// widen only when all constituents of union are widening
+
+var x4 = null || null;
+>x4 : any
+>null || null : null
+>null : null
+>null : null
+
+var y4 = undefined || undefined;
+>y4 : any
+>undefined || undefined : undefined
+>undefined : undefined
+>undefined : undefined
+
+var z4 = void 0 || void 0;
+>z4 : any
+>void 0 || void 0 : undefined
+>void 0 : undefined
+>0 : number
+>void 0 : undefined
+>0 : number
+
+var x5 = null || x2;
+>x5 : null
+>null || x2 : null
+>null : null
+>x2 : null
+
+var y5 = undefined || y2;
+>y5 : undefined
+>undefined || y2 : undefined
+>undefined : undefined
+>y2 : undefined
+
+var z5 = void 0 || y2;
+>z5 : undefined
+>void 0 || y2 : undefined
+>void 0 : undefined
+>0 : number
+>y2 : undefined
+
diff --git a/tests/baselines/reference/logicalAndOperatorWithEveryType.types b/tests/baselines/reference/logicalAndOperatorWithEveryType.types
index b2628eed921..bd913e94da5 100644
--- a/tests/baselines/reference/logicalAndOperatorWithEveryType.types
+++ b/tests/baselines/reference/logicalAndOperatorWithEveryType.types
@@ -623,7 +623,7 @@ var rj8 = a8 && undefined;
 
 var rj9 = null && undefined;
 >rj9 : any
->null && undefined : null
+>null && undefined : undefined
 >null : null
 >undefined : undefined
 
diff --git a/tests/baselines/reference/objectLiteralWidened.js b/tests/baselines/reference/objectLiteralWidened.js
index 4de228cb14a..98d79133540 100644
--- a/tests/baselines/reference/objectLiteralWidened.js
+++ b/tests/baselines/reference/objectLiteralWidened.js
@@ -1,29 +1,61 @@
 //// [objectLiteralWidened.ts]
 // object literal properties are widened to any
 
-var x = {
+var x1 = {
     foo: null,
     bar: undefined
 }
 
-var y = {
+var y1 = {
     foo: null,
     bar: {
         baz: null,
         boo: undefined
     }
+}
+
+// these are not widened
+
+var u: undefined = undefined;
+var n: null = null;
+
+var x2 = {
+    foo: n,
+    bar: u
+}
+
+var y2 = {
+    foo: n,
+    bar: {
+        baz: n,
+        boo: u
+    }
 }
 
 //// [objectLiteralWidened.js]
 // object literal properties are widened to any
-var x = {
+var x1 = {
     foo: null,
     bar: undefined
 };
-var y = {
+var y1 = {
     foo: null,
     bar: {
         baz: null,
         boo: undefined
     }
 };
+// these are not widened
+var u = undefined;
+var n = null;
+var x2 = {
+    foo: n,
+    bar: u
+};
+var y2 = {
+    foo: n,
+    bar: {
+        baz: n,
+        boo: u
+    }
+};
diff --git a/tests/baselines/reference/objectLiteralWidened.symbols b/tests/baselines/reference/objectLiteralWidened.symbols
index 0bf077cd9d8..4b2a1b4a001 100644
--- a/tests/baselines/reference/objectLiteralWidened.symbols
+++ b/tests/baselines/reference/objectLiteralWidened.symbols
@@ -1,22 +1,22 @@
 === tests/cases/conformance/types/typeRelationships/widenedTypes/objectLiteralWidened.ts ===
 // object literal properties are widened to any
 
-var x = {
->x : Symbol(x, Decl(objectLiteralWidened.ts, 2, 3))
+var x1 = {
+>x1 : Symbol(x1, Decl(objectLiteralWidened.ts, 2, 3))
 
     foo: null,
->foo : Symbol(foo, Decl(objectLiteralWidened.ts, 2, 9))
+>foo : Symbol(foo, Decl(objectLiteralWidened.ts, 2, 10))
 
     bar: undefined
 >bar : Symbol(bar, Decl(objectLiteralWidened.ts, 3, 14))
 >undefined : Symbol(undefined)
 }
 
-var y = {
->y : Symbol(y, Decl(objectLiteralWidened.ts, 7, 3))
+var y1 = {
+>y1 : Symbol(y1, Decl(objectLiteralWidened.ts, 7, 3))
 
     foo: null,
->foo : Symbol(foo, Decl(objectLiteralWidened.ts, 7, 9))
+>foo : Symbol(foo, Decl(objectLiteralWidened.ts, 7, 10))
 
     bar: {
 >bar : Symbol(bar, Decl(objectLiteralWidened.ts, 8, 14))
@@ -29,3 +29,44 @@ var y = {
 >undefined : Symbol(undefined)
     }
 }
+
+// these are not widened
+
+var u: undefined = undefined;
+>u : Symbol(u, Decl(objectLiteralWidened.ts, 17, 3))
+>undefined : Symbol(undefined)
+
+var n: null = null;
+>n : Symbol(n, Decl(objectLiteralWidened.ts, 18, 3))
+
+var x2 = {
+>x2 : Symbol(x2, Decl(objectLiteralWidened.ts, 20, 3))
+
+    foo: n,
+>foo : Symbol(foo, Decl(objectLiteralWidened.ts, 20, 10))
+>n : Symbol(n, Decl(objectLiteralWidened.ts, 18, 3))
+
+    bar: u
+>bar : Symbol(bar, Decl(objectLiteralWidened.ts, 21, 11))
+>u : Symbol(u, Decl(objectLiteralWidened.ts, 17, 3))
+}
+
+var y2 = {
+>y2 : Symbol(y2, Decl(objectLiteralWidened.ts, 25, 3))
+
+    foo: n,
+>foo : Symbol(foo, Decl(objectLiteralWidened.ts, 25, 10))
+>n : Symbol(n, Decl(objectLiteralWidened.ts, 18, 3))
+
+    bar: {
+>bar : Symbol(bar, Decl(objectLiteralWidened.ts, 26, 11))
+
+        baz: n,
+>baz : Symbol(baz, Decl(objectLiteralWidened.ts, 27, 10))
+>n : Symbol(n, Decl(objectLiteralWidened.ts, 18, 3))
+
+        boo: u
+>boo : Symbol(boo, Decl(objectLiteralWidened.ts, 28, 15))
+>u : Symbol(u, Decl(objectLiteralWidened.ts, 17, 3))
+    }
+}
diff --git a/tests/baselines/reference/objectLiteralWidened.types b/tests/baselines/reference/objectLiteralWidened.types
index 9f47e47795d..66309202617 100644
--- a/tests/baselines/reference/objectLiteralWidened.types
+++ b/tests/baselines/reference/objectLiteralWidened.types
@@ -1,8 +1,8 @@
 === tests/cases/conformance/types/typeRelationships/widenedTypes/objectLiteralWidened.ts ===
 // object literal properties are widened to any
 
-var x = {
->x : { foo: any; bar: any; }
+var x1 = {
+>x1 : { foo: any; bar: any; }
 >{    foo: null,    bar: undefined} : { foo: null; bar: undefined; }
 
     foo: null,
@@ -14,8 +14,8 @@ var x = {
 >undefined : undefined
 }
 
-var y = {
->y : { foo: any; bar: { baz: any; boo: any; }; }
+var y1 = {
+>y1 : { foo: any; bar: { baz: any; boo: any; }; }
 >{    foo: null,    bar: {        baz: null,        boo: undefined    }} : { foo: null; bar: { baz: null; boo: undefined; }; }
 
     foo: null,
@@ -35,3 +35,49 @@ var y = {
 >undefined : undefined
     }
 }
+
+// these are not widened
+
+var u: undefined = undefined;
+>u : undefined
+>undefined : undefined
+
+var n: null = null;
+>n : null
+>null : null
+>null : null
+
+var x2 = {
+>x2 : { foo: null; bar: undefined; }
+>{    foo: n,    bar: u} : { foo: null; bar: undefined; }
+
+    foo: n,
+>foo : null
+>n : null
+
+    bar: u
+>bar : undefined
+>u : undefined
+}
+
+var y2 = {
+>y2 : { foo: null; bar: { baz: null; boo: undefined; }; }
+>{    foo: n,    bar: {        baz: n,        boo: u    }} : { foo: null; bar: { baz: null; boo: undefined; }; }
+
+    foo: n,
+>foo : null
+>n : null
+
+    bar: {
+>bar : { baz: null; boo: undefined; }
+>{        baz: n,        boo: u    } : { baz: null; boo: undefined; }
+
+        baz: n,
+>baz : null
+>n : null
+
+        boo: u
+>boo : undefined
+>u : undefined
+    }
+}
diff --git a/tests/baselines/reference/strictNullChecksNoWidening.js b/tests/baselines/reference/strictNullChecksNoWidening.js
new file mode 100644
index 00000000000..ba26a04fc13
--- /dev/null
+++ b/tests/baselines/reference/strictNullChecksNoWidening.js
@@ -0,0 +1,31 @@
+//// [strictNullChecksNoWidening.ts]
+
+var a1 = null;
+var a2 = undefined;
+var a3 = void 0;
+
+var b1 = [];
+var b2 = [,];
+var b3 = [undefined];
+var b4 = [[], []];
+var b5 = [[], [,]];
+
+declare function f(x: T): T;
+
+var c1 = f(null);
+var c2 = f(undefined);
+var c3 = f([]);
+
+
+//// [strictNullChecksNoWidening.js]
+var a1 = null;
+var a2 = undefined;
+var a3 = void 0;
+var b1 = [];
+var b2 = [,];
+var b3 = [undefined];
+var b4 = [[], []];
+var b5 = [[], [,]];
+var c1 = f(null);
+var c2 = f(undefined);
+var c3 = f([]);
diff --git a/tests/baselines/reference/strictNullChecksNoWidening.symbols b/tests/baselines/reference/strictNullChecksNoWidening.symbols
new file mode 100644
index 00000000000..a23a0d2e926
--- /dev/null
+++ b/tests/baselines/reference/strictNullChecksNoWidening.symbols
@@ -0,0 +1,48 @@
+=== tests/cases/conformance/types/typeRelationships/widenedTypes/strictNullChecksNoWidening.ts ===
+
+var a1 = null;
+>a1 : Symbol(a1, Decl(strictNullChecksNoWidening.ts, 1, 3))
+
+var a2 = undefined;
+>a2 : Symbol(a2, Decl(strictNullChecksNoWidening.ts, 2, 3))
+>undefined : Symbol(undefined)
+
+var a3 = void 0;
+>a3 : Symbol(a3, Decl(strictNullChecksNoWidening.ts, 3, 3))
+
+var b1 = [];
+>b1 : Symbol(b1, Decl(strictNullChecksNoWidening.ts, 5, 3))
+
+var b2 = [,];
+>b2 : Symbol(b2, Decl(strictNullChecksNoWidening.ts, 6, 3))
+
+var b3 = [undefined];
+>b3 : Symbol(b3, Decl(strictNullChecksNoWidening.ts, 7, 3))
+>undefined : Symbol(undefined)
+
+var b4 = [[], []];
+>b4 : Symbol(b4, Decl(strictNullChecksNoWidening.ts, 8, 3))
+
+var b5 = [[], [,]];
+>b5 : Symbol(b5, Decl(strictNullChecksNoWidening.ts, 9, 3))
+
+declare function f(x: T): T;
+>f : Symbol(f, Decl(strictNullChecksNoWidening.ts, 9, 19))
+>T : Symbol(T, Decl(strictNullChecksNoWidening.ts, 11, 19))
+>x : Symbol(x, Decl(strictNullChecksNoWidening.ts, 11, 22))
+>T : Symbol(T, Decl(strictNullChecksNoWidening.ts, 11, 19))
+>T : Symbol(T, Decl(strictNullChecksNoWidening.ts, 11, 19))
+
+var c1 = f(null);
+>c1 : Symbol(c1, Decl(strictNullChecksNoWidening.ts, 13, 3))
+>f : Symbol(f, Decl(strictNullChecksNoWidening.ts, 9, 19))
+
+var c2 = f(undefined);
+>c2 : Symbol(c2, Decl(strictNullChecksNoWidening.ts, 14, 3))
+>f : Symbol(f, Decl(strictNullChecksNoWidening.ts, 9, 19))
+>undefined : Symbol(undefined)
+
+var c3 = f([]);
+>c3 : Symbol(c3, Decl(strictNullChecksNoWidening.ts, 15, 3))
+>f : Symbol(f, Decl(strictNullChecksNoWidening.ts, 9, 19))
+
diff --git a/tests/baselines/reference/strictNullChecksNoWidening.types b/tests/baselines/reference/strictNullChecksNoWidening.types
new file mode 100644
index 00000000000..6dd2ee3fb4c
--- /dev/null
+++ b/tests/baselines/reference/strictNullChecksNoWidening.types
@@ -0,0 +1,67 @@
+=== tests/cases/conformance/types/typeRelationships/widenedTypes/strictNullChecksNoWidening.ts ===
+
+var a1 = null;
+>a1 : null
+>null : null
+
+var a2 = undefined;
+>a2 : undefined
+>undefined : undefined
+
+var a3 = void 0;
+>a3 : undefined
+>void 0 : undefined
+>0 : number
+
+var b1 = [];
+>b1 : never[]
+>[] : never[]
+
+var b2 = [,];
+>b2 : undefined[]
+>[,] : undefined[]
+> : undefined
+
+var b3 = [undefined];
+>b3 : undefined[]
+>[undefined] : undefined[]
+>undefined : undefined
+
+var b4 = [[], []];
+>b4 : never[][]
+>[[], []] : never[][]
+>[] : never[]
+>[] : never[]
+
+var b5 = [[], [,]];
+>b5 : undefined[][]
+>[[], [,]] : undefined[][]
+>[] : never[]
+>[,] : undefined[]
+> : undefined
+
+declare function f(x: T): T;
+>f : (x: T) => T
+>T : T
+>x : T
+>T : T
+>T : T
+
+var c1 = f(null);
+>c1 : null
+>f(null) : null
+>f : (x: T) => T
+>null : null
+
+var c2 = f(undefined);
+>c2 : undefined
+>f(undefined) : undefined
+>f : (x: T) => T
+>undefined : undefined
+
+var c3 = f([]);
+>c3 : never[]
+>f([]) : never[]
+>f : (x: T) => T
+>[] : never[]
+
diff --git a/tests/baselines/reference/typeGuardsInFunctionAndModuleBlock.js b/tests/baselines/reference/typeGuardsInFunctionAndModuleBlock.js
index 91443a9ae78..8ec45de814e 100644
--- a/tests/baselines/reference/typeGuardsInFunctionAndModuleBlock.js
+++ b/tests/baselines/reference/typeGuardsInFunctionAndModuleBlock.js
@@ -41,7 +41,7 @@ function foo4(x: number | string | boolean) {
                 : x.toString(); // number
         })(x); // x here is narrowed to number | boolean
 }
-// Type guards affect nested function expressions and nested function declarations
+// Type guards do not affect nested function declarations
 function foo5(x: number | string | boolean) {
     if (typeof x === "string") {
         var y = x; // string;
@@ -121,7 +121,7 @@ function foo4(x) {
                 : x.toString(); // number
         })(x); // x here is narrowed to number | boolean
 }
-// Type guards affect nested function expressions and nested function declarations
+// Type guards do not affect nested function declarations
 function foo5(x) {
     if (typeof x === "string") {
         var y = x; // string;
diff --git a/tests/baselines/reference/typeGuardsInFunctionAndModuleBlock.symbols b/tests/baselines/reference/typeGuardsInFunctionAndModuleBlock.symbols
index bf21641624e..56eeb22bf9f 100644
--- a/tests/baselines/reference/typeGuardsInFunctionAndModuleBlock.symbols
+++ b/tests/baselines/reference/typeGuardsInFunctionAndModuleBlock.symbols
@@ -27,9 +27,9 @@ function foo(x: number | string | boolean) {
 >toString : Symbol(Object.toString, Decl(lib.d.ts, --, --))
 
                 : x.toString(); // number
->x.toString : Symbol(toString, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
+>x.toString : Symbol(Number.toString, Decl(lib.d.ts, --, --))
 >x : Symbol(x, Decl(typeGuardsInFunctionAndModuleBlock.ts, 2, 13))
->toString : Symbol(toString, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
+>toString : Symbol(Number.toString, Decl(lib.d.ts, --, --))
 
         } ();
 }
@@ -60,9 +60,9 @@ function foo2(x: number | string | boolean) {
 >toString : Symbol(Object.toString, Decl(lib.d.ts, --, --))
 
                 : x.toString(); // number
->x.toString : Symbol(toString, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
+>x.toString : Symbol(Number.toString, Decl(lib.d.ts, --, --))
 >x : Symbol(x, Decl(typeGuardsInFunctionAndModuleBlock.ts, 12, 14))
->toString : Symbol(toString, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
+>toString : Symbol(Number.toString, Decl(lib.d.ts, --, --))
 
         } (x); // x here is narrowed to number | boolean
 >x : Symbol(x, Decl(typeGuardsInFunctionAndModuleBlock.ts, 12, 14))
@@ -91,9 +91,9 @@ function foo3(x: number | string | boolean) {
 >toString : Symbol(Object.toString, Decl(lib.d.ts, --, --))
 
                 : x.toString(); // number
->x.toString : Symbol(toString, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
+>x.toString : Symbol(Number.toString, Decl(lib.d.ts, --, --))
 >x : Symbol(x, Decl(typeGuardsInFunctionAndModuleBlock.ts, 22, 14))
->toString : Symbol(toString, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
+>toString : Symbol(Number.toString, Decl(lib.d.ts, --, --))
 
         })();
 }
@@ -123,14 +123,14 @@ function foo4(x: number | string | boolean) {
 >toString : Symbol(Object.toString, Decl(lib.d.ts, --, --))
 
                 : x.toString(); // number
->x.toString : Symbol(toString, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
+>x.toString : Symbol(Number.toString, Decl(lib.d.ts, --, --))
 >x : Symbol(x, Decl(typeGuardsInFunctionAndModuleBlock.ts, 32, 14))
->toString : Symbol(toString, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
+>toString : Symbol(Number.toString, Decl(lib.d.ts, --, --))
 
         })(x); // x here is narrowed to number | boolean
 >x : Symbol(x, Decl(typeGuardsInFunctionAndModuleBlock.ts, 32, 14))
 }
-// Type guards affect nested function expressions and nested function declarations
+// Type guards do not affect nested function declarations
 function foo5(x: number | string | boolean) {
 >foo5 : Symbol(foo5, Decl(typeGuardsInFunctionAndModuleBlock.ts, 41, 1))
 >x : Symbol(x, Decl(typeGuardsInFunctionAndModuleBlock.ts, 43, 14))
diff --git a/tests/baselines/reference/typeGuardsInFunctionAndModuleBlock.types b/tests/baselines/reference/typeGuardsInFunctionAndModuleBlock.types
index 67d1816cfc3..cc9553b9767 100644
--- a/tests/baselines/reference/typeGuardsInFunctionAndModuleBlock.types
+++ b/tests/baselines/reference/typeGuardsInFunctionAndModuleBlock.types
@@ -21,14 +21,14 @@ function foo(x: number | string | boolean) {
 >f : () => string
 
             var b = x; // number | boolean
->b : number | string | boolean
->x : number | string | boolean
+>b : number | boolean
+>x : number | boolean
 
             return typeof x === "boolean"
 >typeof x === "boolean"                ? x.toString() // boolean                : x.toString() : string
 >typeof x === "boolean" : boolean
 >typeof x : string
->x : number | string | boolean
+>x : number | boolean
 >"boolean" : string
 
                 ? x.toString() // boolean
@@ -40,7 +40,7 @@ function foo(x: number | string | boolean) {
                 : x.toString(); // number
 >x.toString() : string
 >x.toString : (radix?: number) => string
->x : number | string
+>x : number
 >toString : (radix?: number) => string
 
         } ();
@@ -66,14 +66,14 @@ function foo2(x: number | string | boolean) {
 >a : number | boolean
 
             var b = x; // new scope - number | boolean
->b : number | string | boolean
->x : number | string | boolean
+>b : number | boolean
+>x : number | boolean
 
             return typeof x === "boolean"
 >typeof x === "boolean"                ? x.toString() // boolean                : x.toString() : string
 >typeof x === "boolean" : boolean
 >typeof x : string
->x : number | string | boolean
+>x : number | boolean
 >"boolean" : string
 
                 ? x.toString() // boolean
@@ -85,7 +85,7 @@ function foo2(x: number | string | boolean) {
                 : x.toString(); // number
 >x.toString() : string
 >x.toString : (radix?: number) => string
->x : number | string
+>x : number
 >toString : (radix?: number) => string
 
         } (x); // x here is narrowed to number | boolean
@@ -111,14 +111,14 @@ function foo3(x: number | string | boolean) {
 >() => {            var b = x; // new scope - number | boolean            return typeof x === "boolean"                ? x.toString() // boolean                : x.toString(); // number        } : () => string
 
             var b = x; // new scope - number | boolean
->b : number | string | boolean
->x : number | string | boolean
+>b : number | boolean
+>x : number | boolean
 
             return typeof x === "boolean"
 >typeof x === "boolean"                ? x.toString() // boolean                : x.toString() : string
 >typeof x === "boolean" : boolean
 >typeof x : string
->x : number | string | boolean
+>x : number | boolean
 >"boolean" : string
 
                 ? x.toString() // boolean
@@ -130,7 +130,7 @@ function foo3(x: number | string | boolean) {
                 : x.toString(); // number
 >x.toString() : string
 >x.toString : (radix?: number) => string
->x : number | string
+>x : number
 >toString : (radix?: number) => string
 
         })();
@@ -156,14 +156,14 @@ function foo4(x: number | string | boolean) {
 >a : number | boolean
 
             var b = x; // new scope - number | boolean
->b : number | string | boolean
->x : number | string | boolean
+>b : number | boolean
+>x : number | boolean
 
             return typeof x === "boolean"
 >typeof x === "boolean"                ? x.toString() // boolean                : x.toString() : string
 >typeof x === "boolean" : boolean
 >typeof x : string
->x : number | string | boolean
+>x : number | boolean
 >"boolean" : string
 
                 ? x.toString() // boolean
@@ -175,13 +175,13 @@ function foo4(x: number | string | boolean) {
                 : x.toString(); // number
 >x.toString() : string
 >x.toString : (radix?: number) => string
->x : number | string
+>x : number
 >toString : (radix?: number) => string
 
         })(x); // x here is narrowed to number | boolean
 >x : number | boolean
 }
-// Type guards affect nested function expressions and nested function declarations
+// Type guards do not affect nested function declarations
 function foo5(x: number | string | boolean) {
 >foo5 : (x: number | string | boolean) => void
 >x : number | string | boolean
diff --git a/tests/cases/compiler/controlFlowPropertyDeclarations.ts b/tests/cases/compiler/controlFlowPropertyDeclarations.ts
new file mode 100644
index 00000000000..5a5e9fb96bf
--- /dev/null
+++ b/tests/cases/compiler/controlFlowPropertyDeclarations.ts
@@ -0,0 +1,148 @@
+// Repro from ##8913
+
+declare var require:any;
+
+var HTMLDOMPropertyConfig = require('react/lib/HTMLDOMPropertyConfig');
+
+// Populate property map with ReactJS's attribute and property mappings
+// TODO handle/use .Properties value eg: MUST_USE_PROPERTY is not HTML attr
+for (var propname in HTMLDOMPropertyConfig.Properties) {
+  if (!HTMLDOMPropertyConfig.Properties.hasOwnProperty(propname)) {
+    continue;
+  }
+
+  var mapFrom = HTMLDOMPropertyConfig.DOMAttributeNames[propname] || propname.toLowerCase();
+}
+
+/**
+ * Repeats a string a certain number of times.
+ * Also: the future is bright and consists of native string repetition:
+ * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat
+ *
+ * @param {string} string  String to repeat
+ * @param {number} times   Number of times to repeat string. Integer.
+ * @see http://jsperf.com/string-repeater/2
+ */
+function repeatString(string, times) {
+  if (times === 1) {
+    return string;
+  }
+  if (times < 0) { throw new Error(); }
+  var repeated = '';
+  while (times) {
+    if (times & 1) {
+      repeated += string;
+    }
+    if (times >>= 1) {
+      string += string;
+    }
+  }
+  return repeated;
+}
+
+/**
+ * Determine if the string ends with the specified substring.
+ *
+ * @param {string} haystack String to search in
+ * @param {string} needle   String to search for
+ * @return {boolean}
+ */
+function endsWith(haystack, needle) {
+  return haystack.slice(-needle.length) === needle;
+}
+
+/**
+ * Trim the specified substring off the string. If the string does not end
+ * with the specified substring, this is a no-op.
+ *
+ * @param {string} haystack String to search in
+ * @param {string} needle   String to search for
+ * @return {string}
+ */
+function trimEnd(haystack, needle) {
+  return endsWith(haystack, needle)
+    ? haystack.slice(0, -needle.length)
+    : haystack;
+}
+
+/**
+ * Convert a hyphenated string to camelCase.
+ */
+function hyphenToCamelCase(string) {
+  return string.replace(/-(.)/g, function(match, chr) {
+    return chr.toUpperCase();
+  });
+}
+
+/**
+ * Determines if the specified string consists entirely of whitespace.
+ */
+function isEmpty(string) {
+   return !/[^\s]/.test(string);
+}
+
+/**
+ * Determines if the CSS value can be converted from a
+ * 'px' suffixed string to a numeric value
+ *
+ * @param {string} value CSS property value
+ * @return {boolean}
+ */
+function isConvertiblePixelValue(value) {
+  return /^\d+px$/.test(value);
+}
+
+export class HTMLtoJSX {
+    private output: string;
+    private level: number;
+    private _inPreTag: boolean;
+
+
+  /**
+   * Handles processing of the specified text node
+   *
+   * @param {TextNode} node
+   */
+  _visitText = (node) => {
+    var parentTag = node.parentNode && node.parentNode.tagName.toLowerCase();
+    if (parentTag === 'textarea' || parentTag === 'style') {
+      // Ignore text content of textareas and styles, as it will have already been moved
+      // to a "defaultValue" attribute and "dangerouslySetInnerHTML" attribute respectively.
+      return;
+    }
+
+    var text = ''
+
+    if (this._inPreTag) {
+      // If this text is contained within a 
, we need to ensure the JSX
+      // whitespace coalescing rules don't eat the whitespace. This means
+      // wrapping newlines and sequences of two or more spaces in variables.
+      text = text
+        .replace(/\r/g, '')
+        .replace(/( {2,}|\n|\t|\{|\})/g, function(whitespace) {
+          return '{' + JSON.stringify(whitespace) + '}';
+        });
+    } else {
+      // If there's a newline in the text, adjust the indent level
+      if (text.indexOf('\n') > -1) {
+      }
+    }
+    this.output += text;
+  }
+
+
+
+};
+
+/**
+ * Handles parsing of inline styles
+ */
+export class StyleParser {
+  styles = {};
+  toJSXString = () => {
+    for (var key in this.styles) {
+      if (!this.styles.hasOwnProperty(key)) {
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/tests/cases/conformance/controlFlow/constLocalsInFunctionExpressions.ts b/tests/cases/conformance/controlFlow/constLocalsInFunctionExpressions.ts
new file mode 100644
index 00000000000..69b19fcb307
--- /dev/null
+++ b/tests/cases/conformance/controlFlow/constLocalsInFunctionExpressions.ts
@@ -0,0 +1,38 @@
+declare function getStringOrNumber(): string | number;
+
+function f1() {
+    const x = getStringOrNumber();
+    if (typeof x === "string") {
+        const f = () => x.length;
+    }
+}
+
+function f2() {
+    const x = getStringOrNumber();
+    if (typeof x !== "string") {
+        return;
+    }
+    const f = () => x.length;
+}
+
+function f3() {
+    const x = getStringOrNumber();
+    if (typeof x === "string") {
+        const f = function() { return x.length; };
+    }
+}
+
+function f4() {
+    const x = getStringOrNumber();
+    if (typeof x !== "string") {
+        return;
+    }
+    const f = function() { return x.length; };
+}
+
+function f5() {
+    const x = getStringOrNumber();
+    if (typeof x === "string") {
+        const f = () => () => x.length;
+    }
+}
\ No newline at end of file
diff --git a/tests/cases/conformance/controlFlow/controlFlowIIFE.ts b/tests/cases/conformance/controlFlow/controlFlowIIFE.ts
new file mode 100644
index 00000000000..c72f038c1ec
--- /dev/null
+++ b/tests/cases/conformance/controlFlow/controlFlowIIFE.ts
@@ -0,0 +1,48 @@
+// @strictNullChecks: true
+
+declare function getStringOrNumber(): string | number;
+
+function f1() {
+    let x = getStringOrNumber();
+    if (typeof x === "string") {
+        let n = function() {
+            return x.length;
+        }();
+    }
+}
+
+function f2() {
+    let x = getStringOrNumber();
+    if (typeof x === "string") {
+        let n = (function() {
+            return x.length;
+        })();
+    }
+}
+
+function f3() {
+    let x = getStringOrNumber();
+    let y: number;
+    if (typeof x === "string") {
+        let n = (z => x.length + y + z)(y = 1);
+    }
+}
+
+// Repros from #8381
+
+let maybeNumber: number | undefined;
+(function () {
+    maybeNumber = 1;
+})();
+maybeNumber++;
+if (maybeNumber !== undefined) {
+    maybeNumber++;
+}
+
+let test: string | undefined;
+if (!test) {
+    throw new Error('Test is not defined');
+}
+(() => {
+    test.slice(1); // No error
+})();
\ No newline at end of file
diff --git a/tests/cases/conformance/expressions/typeGuards/typeGuardsInFunctionAndModuleBlock.ts b/tests/cases/conformance/expressions/typeGuards/typeGuardsInFunctionAndModuleBlock.ts
index 5dbc925d04b..f1ba854394b 100644
--- a/tests/cases/conformance/expressions/typeGuards/typeGuardsInFunctionAndModuleBlock.ts
+++ b/tests/cases/conformance/expressions/typeGuards/typeGuardsInFunctionAndModuleBlock.ts
@@ -40,7 +40,7 @@ function foo4(x: number | string | boolean) {
                 : x.toString(); // number
         })(x); // x here is narrowed to number | boolean
 }
-// Type guards affect nested function expressions and nested function declarations
+// Type guards do not affect nested function declarations
 function foo5(x: number | string | boolean) {
     if (typeof x === "string") {
         var y = x; // string;
diff --git a/tests/cases/conformance/types/typeRelationships/widenedTypes/arrayLiteralWidened.ts b/tests/cases/conformance/types/typeRelationships/widenedTypes/arrayLiteralWidened.ts
index 8af0a5842cc..05428422129 100644
--- a/tests/cases/conformance/types/typeRelationships/widenedTypes/arrayLiteralWidened.ts
+++ b/tests/cases/conformance/types/typeRelationships/widenedTypes/arrayLiteralWidened.ts
@@ -1,6 +1,7 @@
 // array literals are widened upon assignment according to their element type
 
 var a = []; // any[]
+var a = [,,];
 
 var a = [null, null];
 var a = [undefined, undefined];
@@ -11,3 +12,11 @@ var b = [[undefined, undefined]];
 
 var c = [[[]]]; // any[][][]
 var c = [[[null]],[undefined]]
+
+// no widening when one or more elements are non-widening
+
+var x: undefined = undefined;
+
+var d = [x];
+var d = [, x];
+var d = [undefined, x];
diff --git a/tests/cases/conformance/types/typeRelationships/widenedTypes/initializersWidened.ts b/tests/cases/conformance/types/typeRelationships/widenedTypes/initializersWidened.ts
index e79cdc9e168..2eeb96194b7 100644
--- a/tests/cases/conformance/types/typeRelationships/widenedTypes/initializersWidened.ts
+++ b/tests/cases/conformance/types/typeRelationships/widenedTypes/initializersWidened.ts
@@ -1,4 +1,24 @@
 // these are widened to any at the point of assignment
 
-var x = null;
-var y = undefined;
\ No newline at end of file
+var x1 = null;
+var y1 = undefined;
+var z1 = void 0;
+
+// these are not widened
+
+var x2: null;
+var y2: undefined;
+
+var x3: null = null;
+var y3: undefined = undefined;
+var z3: undefined = void 0;
+
+// widen only when all constituents of union are widening
+
+var x4 = null || null;
+var y4 = undefined || undefined;
+var z4 = void 0 || void 0;
+
+var x5 = null || x2;
+var y5 = undefined || y2;
+var z5 = void 0 || y2;
\ No newline at end of file
diff --git a/tests/cases/conformance/types/typeRelationships/widenedTypes/objectLiteralWidened.ts b/tests/cases/conformance/types/typeRelationships/widenedTypes/objectLiteralWidened.ts
index cde44f91168..8b51e526882 100644
--- a/tests/cases/conformance/types/typeRelationships/widenedTypes/objectLiteralWidened.ts
+++ b/tests/cases/conformance/types/typeRelationships/widenedTypes/objectLiteralWidened.ts
@@ -1,14 +1,32 @@
 // object literal properties are widened to any
 
-var x = {
+var x1 = {
     foo: null,
     bar: undefined
 }
 
-var y = {
+var y1 = {
     foo: null,
     bar: {
         baz: null,
         boo: undefined
     }
+}
+
+// these are not widened
+
+var u: undefined = undefined;
+var n: null = null;
+
+var x2 = {
+    foo: n,
+    bar: u
+}
+
+var y2 = {
+    foo: n,
+    bar: {
+        baz: n,
+        boo: u
+    }
 }
\ No newline at end of file
diff --git a/tests/cases/conformance/types/typeRelationships/widenedTypes/strictNullChecksNoWidening.ts b/tests/cases/conformance/types/typeRelationships/widenedTypes/strictNullChecksNoWidening.ts
new file mode 100644
index 00000000000..8f5b4709abf
--- /dev/null
+++ b/tests/cases/conformance/types/typeRelationships/widenedTypes/strictNullChecksNoWidening.ts
@@ -0,0 +1,17 @@
+// @strictNullChecks: true
+
+var a1 = null;
+var a2 = undefined;
+var a3 = void 0;
+
+var b1 = [];
+var b2 = [,];
+var b3 = [undefined];
+var b4 = [[], []];
+var b5 = [[], [,]];
+
+declare function f(x: T): T;
+
+var c1 = f(null);
+var c2 = f(undefined);
+var c3 = f([]);
diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts
index 8a0d2d36364..71e1c3cc5d7 100644
--- a/tests/cases/fourslash/fourslash.ts
+++ b/tests/cases/fourslash/fourslash.ts
@@ -246,6 +246,7 @@ declare namespace FourSlashInterface {
         copyFormatOptions(): FormatCodeOptions;
         setFormatOptions(options: FormatCodeOptions): any;
         selection(startMarker: string, endMarker: string): void;
+        onType(posMarker: string, key: string): void;
         setOption(name: string, value: number): any;
         setOption(name: string, value: string): any;
         setOption(name: string, value: boolean): any;
diff --git a/tests/cases/fourslash/server/formatOnEnter.ts b/tests/cases/fourslash/server/formatOnEnter.ts
new file mode 100644
index 00000000000..f505cf6cec7
--- /dev/null
+++ b/tests/cases/fourslash/server/formatOnEnter.ts
@@ -0,0 +1,17 @@
+/// 
+
+/////*3*/function listAPIFiles (path : string): string[] {
+////    /*1*/
+////    /*2*/
+////}
+
+goTo.marker("1");
+format.onType("1", "\n");
+verify.currentLineContentIs("    ");
+
+goTo.marker("2");
+format.onType("2", "\n");
+verify.currentLineContentIs("    ");
+
+goTo.marker("3");
+verify.currentLineContentIs("function listAPIFiles(path: string): string[] {");
\ No newline at end of file
diff --git a/tests/cases/fourslash/server/jsdocTypedefTag.ts b/tests/cases/fourslash/server/jsdocTypedefTag.ts
new file mode 100644
index 00000000000..e645b518020
--- /dev/null
+++ b/tests/cases/fourslash/server/jsdocTypedefTag.ts
@@ -0,0 +1,64 @@
+/// 
+
+// @allowNonTsExtensions: true
+// @Filename: jsdocCompletion_typedef.js
+
+//// /** @typedef {(string | number)} NumberLike */
+////
+//// /**
+////  * @typedef Animal
+////  * @type {Object}
+////  * @property {string} animalName
+////  * @property {number} animalAge
+////  */
+////
+//// /**
+////  * @typedef {Object} Person
+////  * @property {string} personName
+////  * @property {number} personAge
+////  */
+////
+//// /**
+////  * @typedef {Object}
+////  * @property {string} catName
+////  * @property {number} catAge
+////  */
+//// var Cat;
+////
+//// /** @typedef {{ dogName: string, dogAge: number }} */
+//// var Dog;
+////
+//// /** @type {NumberLike} */
+//// var numberLike; numberLike./*numberLike*/
+////
+//// /** @type {Person} */
+//// var p;p./*person*/
+////
+//// /** @type {Animal} */
+//// var a;a./*animal*/
+////
+//// /** @type {Cat} */
+//// var c;c./*cat*/
+////
+//// /** @type {Dog} */
+//// var d;d./*dog*/
+
+goTo.marker('numberLike');
+verify.memberListContains('charAt');
+verify.memberListContains('toExponential');
+
+goTo.marker('person');
+verify.memberListContains('personName');
+verify.memberListContains('personAge');
+
+goTo.marker('animal');
+verify.memberListContains('animalName');
+verify.memberListContains('animalAge');
+
+goTo.marker('dog');
+verify.memberListContains('dogName');
+verify.memberListContains('dogAge');
+
+goTo.marker('cat');
+verify.memberListContains('catName');
+verify.memberListContains('catAge');
\ No newline at end of file
diff --git a/tests/cases/fourslash/server/jsdocTypedefTagGoToDefinition.ts b/tests/cases/fourslash/server/jsdocTypedefTagGoToDefinition.ts
new file mode 100644
index 00000000000..4db14611938
--- /dev/null
+++ b/tests/cases/fourslash/server/jsdocTypedefTagGoToDefinition.ts
@@ -0,0 +1,29 @@
+/// 
+
+// @allowNonTsExtensions: true
+// @Filename: jsdocCompletion_typedef.js
+
+//// /**
+////  * @typedef {Object} Person
+////  * /*1*/@property {string} personName
+////  * @property {number} personAge
+////  */
+////
+//// /**
+////  * @typedef {{ /*2*/animalName: string, animalAge: number }} Animal
+////  */
+////
+//// /** @type {Person} */
+//// var person; person.personName/*3*/
+////
+//// /** @type {Animal} */
+//// var animal; animal.animalName/*4*/
+
+goTo.file('jsdocCompletion_typedef.js');
+goTo.marker('3');
+goTo.definition();
+verify.caretAtMarker('1');
+
+goTo.marker('4');
+goTo.definition();
+verify.caretAtMarker('2');
diff --git a/tests/cases/fourslash/server/jsdocTypedefTagNavigateTo.ts b/tests/cases/fourslash/server/jsdocTypedefTagNavigateTo.ts
new file mode 100644
index 00000000000..77cd75aa44c
--- /dev/null
+++ b/tests/cases/fourslash/server/jsdocTypedefTagNavigateTo.ts
@@ -0,0 +1,30 @@
+/// 
+
+// @allowNonTsExtensions: true
+// @Filename: jsDocTypedef_form2.js
+////
+//// /** @typedef {(string | number)} NumberLike */
+//// /** @typedef {(string | number | string[])} */
+//// var NumberLike2;
+////
+//// /** @type {/*1*/NumberLike} */
+//// var numberLike;
+
+verify.navigationBar([
+  {
+    "text": "NumberLike",
+    "kind": "type"
+  },
+  {
+    "text": "NumberLike2",
+    "kind": "type"
+  },
+  {
+    "text": "NumberLike2",
+    "kind": "var"
+  },
+  {
+    "text": "numberLike",
+    "kind": "var"
+  }
+]);
\ No newline at end of file
diff --git a/tests/cases/fourslash/server/jsdocTypedefTagRename01.ts b/tests/cases/fourslash/server/jsdocTypedefTagRename01.ts
new file mode 100644
index 00000000000..776d0180b06
--- /dev/null
+++ b/tests/cases/fourslash/server/jsdocTypedefTagRename01.ts
@@ -0,0 +1,20 @@
+/// 
+
+// @allowNonTsExtensions: true
+// @Filename: jsDocTypedef_form1.js
+////
+//// /** @typedef {(string | number)} */
+//// var /*1*/[|NumberLike|];
+////
+//// /*2*/[|NumberLike|] = 10;
+////
+//// /** @type {/*3*/[|NumberLike|]} */
+//// var numberLike;
+
+goTo.file('jsDocTypedef_form1.js')
+goTo.marker('1');
+verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ true);
+goTo.marker('2');
+verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ true);
+goTo.marker('3');
+verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ true);
\ No newline at end of file
diff --git a/tests/cases/fourslash/server/jsdocTypedefTagRename02.ts b/tests/cases/fourslash/server/jsdocTypedefTagRename02.ts
new file mode 100644
index 00000000000..7f1d422d971
--- /dev/null
+++ b/tests/cases/fourslash/server/jsdocTypedefTagRename02.ts
@@ -0,0 +1,15 @@
+/// 
+
+// @allowNonTsExtensions: true
+// @Filename: jsDocTypedef_form2.js
+////
+//// /** @typedef {(string | number)} /*1*/[|NumberLike|] */
+////
+//// /** @type {/*2*/[|NumberLike|]} */
+//// var numberLike;
+
+goTo.file('jsDocTypedef_form2.js')
+goTo.marker('1');
+verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ true);
+goTo.marker('2');
+verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ true);
\ No newline at end of file
diff --git a/tests/cases/fourslash/server/jsdocTypedefTagRename03.ts b/tests/cases/fourslash/server/jsdocTypedefTagRename03.ts
new file mode 100644
index 00000000000..c1b38945806
--- /dev/null
+++ b/tests/cases/fourslash/server/jsdocTypedefTagRename03.ts
@@ -0,0 +1,20 @@
+/// 
+
+// @allowNonTsExtensions: true
+// @Filename: jsDocTypedef_form3.js
+////
+//// /** 
+////  * @typedef /*1*/[|Person|]
+////  * @type {Object}
+////  * @property {number} age
+////  * @property {string} name
+////  */
+////
+//// /** @type {/*2*/[|Person|]} */
+//// var person;
+
+goTo.file('jsDocTypedef_form3.js')
+goTo.marker('1');
+verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ true);
+goTo.marker('2');
+verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ true);
\ No newline at end of file
diff --git a/tests/cases/fourslash/server/jsdocTypedefTagRename04.ts b/tests/cases/fourslash/server/jsdocTypedefTagRename04.ts
new file mode 100644
index 00000000000..b7bf220512a
--- /dev/null
+++ b/tests/cases/fourslash/server/jsdocTypedefTagRename04.ts
@@ -0,0 +1,24 @@
+/// 
+
+// @allowNonTsExtensions: true
+// @Filename: jsDocTypedef_form2.js
+////
+//// function test1() {
+////    /** @typedef {(string | number)} NumberLike */
+////
+////    /** @type {/*1*/NumberLike} */
+////    var numberLike;
+//// }
+//// function test2() {
+////    /** @typedef {(string | number)} NumberLike2 */
+////
+////    /** @type {NumberLike2} */
+////    var n/*2*/umberLike2;
+//// }
+
+goTo.marker('2');
+verify.quickInfoExists();
+goTo.marker('1');
+edit.insert('111');
+goTo.marker('2');
+verify.quickInfoExists();
\ No newline at end of file
diff --git a/tests/cases/fourslash/syntacticClassificationForJSDocTemplateTag.ts b/tests/cases/fourslash/syntacticClassificationForJSDocTemplateTag.ts
new file mode 100644
index 00000000000..c3368207d2c
--- /dev/null
+++ b/tests/cases/fourslash/syntacticClassificationForJSDocTemplateTag.ts
@@ -0,0 +1,22 @@
+/// 
+
+/////** @template T */
+////function ident: T {
+////}
+
+var c = classification;
+verify.syntacticClassificationsAre(
+    c.comment("/** "),
+    c.punctuation("@"),
+    c.docCommentTagName("template"),
+    c.typeParameterName("T"),
+    c.comment(" */"),
+    c.keyword("function"),
+    c.identifier("ident"),
+    c.punctuation("<"),
+    c.typeParameterName("T"),
+    c.punctuation(">"),
+    c.punctuation(":"),
+    c.identifier("T"),
+    c.punctuation("{"),
+    c.punctuation("}"));
diff --git a/tests/cases/unittests/jsDocParsing.ts b/tests/cases/unittests/jsDocParsing.ts
index 22f679c4ad5..fb75b4d4940 100644
--- a/tests/cases/unittests/jsDocParsing.ts
+++ b/tests/cases/unittests/jsDocParsing.ts
@@ -1004,7 +1004,8 @@ namespace ts {
                 if (result !== expected) {
                     // Turn on a human-readable diff
                     if (typeof require !== "undefined") {
-                        require("chai").config.showDiff = true;
+                        const chai = require("chai");
+                        chai.config.showDiff = true;
                         chai.expect(JSON.parse(result)).equal(JSON.parse(expected));
                     }
                     else {
@@ -2218,4 +2219,4 @@ namespace ts {
             });
         });
     });
-}
+}
\ No newline at end of file
diff --git a/tests/cases/unittests/tsserverProjectSystem.ts b/tests/cases/unittests/tsserverProjectSystem.ts
new file mode 100644
index 00000000000..c69821ced5f
--- /dev/null
+++ b/tests/cases/unittests/tsserverProjectSystem.ts
@@ -0,0 +1,294 @@
+/// 
+
+namespace ts {
+    function notImplemented(): any {
+        throw new Error("Not yet implemented");
+    }
+
+    const nullLogger: server.Logger = {
+        close: () => void 0,
+        isVerbose: () => void 0,
+        loggingEnabled: () => false,
+        perftrc: () => void 0,
+        info: () => void 0,
+        startGroup: () => void 0,
+        endGroup: () => void 0,
+        msg: () => void 0
+    };
+
+    const { content: libFileContent } = Harness.getDefaultLibraryFile(Harness.IO);
+
+    function getExecutingFilePathFromLibFile(libFile: FileOrFolder): string {
+        return combinePaths(getDirectoryPath(libFile.path), "tsc.js");
+    }
+
+    interface FileOrFolder {
+        path: string;
+        content?: string;
+    }
+
+    interface FSEntry {
+        path: Path;
+        fullPath: string;
+    }
+
+    interface File extends FSEntry {
+        content: string;
+    }
+
+    interface Folder extends FSEntry {
+        entries: FSEntry[];
+    }
+
+    function isFolder(s: FSEntry): s is Folder {
+        return isArray((s).entries);
+    }
+
+    function isFile(s: FSEntry): s is File {
+        return typeof (s).content === "string";
+    }
+
+    function addFolder(fullPath: string, toPath: (s: string) => Path, fs: FileMap): Folder {
+        const path = toPath(fullPath);
+        if (fs.contains(path)) {
+            Debug.assert(isFolder(fs.get(path)));
+            return (fs.get(path));
+        }
+
+        const entry: Folder = { path, entries: [], fullPath };
+        fs.set(path, entry);
+
+        const baseFullPath = getDirectoryPath(fullPath);
+        if (fullPath !== baseFullPath) {
+            addFolder(baseFullPath, toPath, fs).entries.push(entry);
+        }
+
+        return entry;
+    }
+
+    function sizeOfMap(map: Map): number {
+        let n = 0;
+        for (const name in map) {
+            if (hasProperty(map, name)) {
+                n++;
+            }
+        }
+        return n;
+    }
+
+    function checkMapKeys(caption: string, map: Map, expectedKeys: string[]) {
+        assert.equal(sizeOfMap(map), expectedKeys.length, `${caption}: incorrect size of map`);
+        for (const name of expectedKeys) {
+            assert.isTrue(hasProperty(map, name), `${caption} is expected to contain ${name}, actual keys: ${getKeys(map)}`);
+        }
+    }
+
+    function checkFileNames(caption: string, actualFileNames: string[], expectedFileNames: string[]) {
+        assert.equal(actualFileNames.length, expectedFileNames.length, `${caption}: incorrect actual number of files, expected ${JSON.stringify(expectedFileNames)}, got ${actualFileNames}`);
+        for (const f of expectedFileNames) {
+            assert.isTrue(contains(actualFileNames, f), `${caption}: expected to find ${f} in ${JSON.stringify(actualFileNames)}`);
+        }
+    }
+
+    function readDirectory(folder: FSEntry, ext: string, excludes: Path[], result: string[]): void {
+        if (!folder || !isFolder(folder) || contains(excludes, folder.path)) {
+            return;
+        }
+        for (const entry of folder.entries) {
+            if (contains(excludes, entry.path)) {
+                continue;
+            }
+            if (isFolder(entry)) {
+                readDirectory(entry, ext, excludes, result);
+            }
+            else if (fileExtensionIs(entry.path, ext)) {
+                result.push(entry.fullPath);
+            }
+        }
+    }
+
+    class TestServerHost implements server.ServerHost {
+        args: string[] = [];
+        newLine: "\n";
+
+        private fs: ts.FileMap;
+        private getCanonicalFileName: (s: string) => string;
+        private toPath: (f: string) => Path;
+        readonly watchedDirectories: Map<{ cb: DirectoryWatcherCallback, recursive: boolean }[]> = {};
+        readonly watchedFiles: Map = {};
+
+        constructor(public useCaseSensitiveFileNames: boolean, private executingFilePath: string, private currentDirectory: string, fileOrFolderList: FileOrFolder[]) {
+            this.getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames);
+            this.toPath = s => toPath(s, currentDirectory, this.getCanonicalFileName);
+
+            this.reloadFS(fileOrFolderList);
+        }
+
+        reloadFS(filesOrFolders: FileOrFolder[]) {
+            this.fs = createFileMap();
+            for (const fileOrFolder of filesOrFolders) {
+                const path = this.toPath(fileOrFolder.path);
+                const fullPath = getNormalizedAbsolutePath(fileOrFolder.path, this.currentDirectory);
+                if (typeof fileOrFolder.content === "string") {
+                    const entry = { path, content: fileOrFolder.content, fullPath };
+                    this.fs.set(path, entry);
+                    addFolder(getDirectoryPath(fullPath), this.toPath, this.fs).entries.push(entry);
+                }
+                else {
+                    addFolder(fullPath, this.toPath, this.fs);
+                }
+            }
+        }
+
+        fileExists(s: string) {
+            const path = this.toPath(s);
+            return this.fs.contains(path) && isFile(this.fs.get(path));
+        };
+
+        directoryExists(s: string) {
+            const path = this.toPath(s);
+            return this.fs.contains(path) && isFolder(this.fs.get(path));
+        }
+
+        getDirectories(s: string) {
+            const path = this.toPath(s);
+            if (!this.fs.contains(path)) {
+                return [];
+            }
+            else {
+                const entry = this.fs.get(path);
+                return isFolder(entry) ? map(entry.entries, x => getBaseFileName(x.fullPath)) : [];
+            }
+        }
+
+        readDirectory(path: string, ext: string, excludes: string[]): string[] {
+            const result: string[] = [];
+            readDirectory(this.fs.get(this.toPath(path)), ext, map(excludes, e => toPath(e, path, this.getCanonicalFileName)), result);
+            return result;
+        }
+
+        watchDirectory(directoryName: string, callback: DirectoryWatcherCallback, recursive: boolean): DirectoryWatcher {
+            const path = this.toPath(directoryName);
+            const callbacks = lookUp(this.watchedDirectories, path) || (this.watchedDirectories[path] = []);
+            callbacks.push({ cb: callback, recursive });
+            return {
+                referenceCount: 0,
+                directoryName,
+                close: () => {
+                    for (let i = 0; i < callbacks.length; i++) {
+                        if (callbacks[i].cb === callback) {
+                            callbacks.splice(i, 1);
+                            break;
+                        }
+                    }
+                    if (!callbacks.length) {
+                        delete this.watchedDirectories[path];
+                    }
+                }
+            };
+        }
+
+        watchFile(fileName: string, callback: FileWatcherCallback) {
+            const path = this.toPath(fileName);
+            const callbacks = lookUp(this.watchedFiles, path) || (this.watchedFiles[path] = []);
+            callbacks.push(callback);
+            return {
+                close: () => {
+                    const i = callbacks.indexOf(callback);
+                    callbacks.splice(i, 1);
+                    if (!callbacks.length) {
+                        delete this.watchedFiles[path];
+                    }
+                }
+            };
+        }
+
+        // TOOD: record and invoke callbacks to simulate timer events
+        readonly setTimeout = (callback: (...args: any[]) => void, ms: number, ...args: any[]): any => void 0;
+        readonly clearTimeout = (timeoutId: any): void => void 0;
+        readonly readFile = (s: string) => (this.fs.get(this.toPath(s))).content;
+        readonly resolvePath = (s: string) => s;
+        readonly getExecutingFilePath = () => this.executingFilePath;
+        readonly getCurrentDirectory = () => this.currentDirectory;
+        readonly writeFile = (path: string, content: string) => notImplemented();
+        readonly write = (s: string) => notImplemented();
+        readonly createDirectory = (s: string) => notImplemented();
+        readonly exit = () => notImplemented();
+    }
+
+    describe("tsserver project system:", () => {
+        it("create inferred project", () => {
+            const appFile: FileOrFolder = {
+                path: "/a/b/c/app.ts",
+                content: `
+                import {f} from "./module"
+                console.log(f)
+                `
+            };
+            const libFile: FileOrFolder = {
+                path: "/a/lib/lib.d.ts",
+                content: libFileContent
+            };
+            const moduleFile: FileOrFolder = {
+                path: "/a/b/c/module.d.ts",
+                content: `export let x: number`
+            };
+            const host = new TestServerHost(/*useCaseSensitiveFileNames*/ false, getExecutingFilePathFromLibFile(libFile), "/", [appFile, moduleFile, libFile]);
+            const projectService = new server.ProjectService(host, nullLogger);
+            const { configFileName } = projectService.openClientFile(appFile.path);
+
+            assert(!configFileName, `should not find config, got: '${configFileName}`);
+            assert.equal(projectService.inferredProjects.length, 1, "expected one inferred project");
+            assert.equal(projectService.configuredProjects.length, 0, "expected no configured project");
+
+            const project = projectService.inferredProjects[0];
+
+            checkFileNames("inferred project", project.getFileNames(), [appFile.path, libFile.path, moduleFile.path]);
+            checkMapKeys("watchedDirectories", host.watchedDirectories, ["/a/b/c", "/a/b", "/a"]);
+        });
+
+        it("create configured project without file list", () => {
+            const configFile: FileOrFolder = {
+                path: "/a/b/tsconfig.json",
+                content: `
+                {
+                    "compilerOptions": {},
+                    "exclude": [
+                        "e"
+                    ]
+                }`
+            };
+            const libFile: FileOrFolder = {
+                path: "/a/lib/lib.d.ts",
+                content: libFileContent
+            };
+            const file1: FileOrFolder = {
+                path: "/a/b/c/f1.ts",
+                content: "let x = 1"
+            };
+            const file2: FileOrFolder = {
+                path: "/a/b/d/f2.ts",
+                content: "let y = 1"
+            };
+            const file3: FileOrFolder = {
+                path: "/a/b/e/f3.ts",
+                content: "let z = 1"
+            };
+            const host = new TestServerHost(/*useCaseSensitiveFileNames*/ false, getExecutingFilePathFromLibFile(libFile), "/", [ configFile, libFile, file1, file2, file3 ]);
+            const projectService = new server.ProjectService(host, nullLogger);
+            const { configFileName, configFileErrors } = projectService.openClientFile(file1.path);
+
+            assert(configFileName, "should find config file");
+            assert.isTrue(!configFileErrors, `expect no errors in config file, got ${JSON.stringify(configFileErrors)}`);
+            assert.equal(projectService.inferredProjects.length, 0, "expected no inferred project");
+            assert.equal(projectService.configuredProjects.length, 1, "expected one configured project");
+
+            const project = projectService.configuredProjects[0];
+            checkFileNames("configuredProjects project, actualFileNames", project.getFileNames(), [file1.path, libFile.path, file2.path]);
+            checkFileNames("configuredProjects project, rootFileNames", project.getRootFiles(), [file1.path, file2.path]);
+
+            checkMapKeys("watchedFiles", host.watchedFiles, [configFile.path, file2.path, libFile.path]); // watching all files except one that was open
+            checkMapKeys("watchedDirectories", host.watchedDirectories, [getDirectoryPath(configFile.path)]);
+        });
+    });
+}
\ No newline at end of file